初识Java 17-4 反射

本笔记参考自: 《On Java 中文版》


接口和类型信息

        interface关键字的一个重要目标就是允许程序员隔离组件,减少耦合。但我们可以通过类型信息来绕过接口的隔离,这使得接口不一定能够保证解耦。

        为了演示这一实现,我们需要先创建一个接口:

package reflection.interfacea;

public interface A {
    void f();
}

        接下来的例子展示了如何绕过接口,偷偷访问实际的实现类型:

package reflection;

import reflection.interfacea.A;

class B implements A {
    @Override
    public void f() {
    }

    public void g() {
    }
}

public class InterfaceViolation {
    public static void main(String[] args) {
        A a = new B();
        a.f();
        // a.g(); // 此时还不能访问方法g()

        System.out.println(a.getClass().getName());
        if (a instanceof B) {
            B b = (B) a;
            b.g();
        }
    }
}

        通过反射,我们将a强制转换成了B类,以此来调用A中不存在的方法。

        很显然,这种实现是合理的。但当一些客户程序员使用这些代码时,他们也可能会通过这种方式的调用,使得其代码与我们的代码之间的耦合程度超出我们的预期。换言之,instanceof并不能够保护我们的代码。

    Windows系统就存在类似的问题……

        这时有两种解决方案:①直接声明,让客户程序员自己承当使用额外代码带来的后果。②而另一种方法,就是对代码的访问权限加以控制:

【例子:通过包访问权限隔绝包外的访问】

package reflection.packageaccess;

import reflection.interfacea.A;

class C implements A {
    @Override
    public void f() {
        System.out.println("public C.f()");
    }

    public void g() {
        System.out.println("public C.g()");
    }

    void u() {
        System.out.println("package C.u()");
    }

    protected void v() {
        System.out.println("protected C.v()");
    }

    private void w() {
        System.out.println("private C.w()");
    }
}

public class HiddenC {
    public static A makeA() {
        return new C();
    }
}

        我们创建了一个接口A的实现:C类,并将其放到一个单独的包中。代码中只有HiddenC类存在一个与外界通信的接口makeA()

        遗憾的是,我们依旧有办法绕过包的隐藏:

【例子:绕过包隐藏】

package reflection;

import reflection.packageaccess.HiddenC;
import reflection.interfacea.A;

import java.lang.reflect.Method;

public class HiddenImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = HiddenC.makeA();
        a.f();
        // 通过反射可以得到隐藏的类名
        System.out.println(a.getClass().getName());
        // 编译错误,无法找到"C":
        /* if (a instanceof C) {
            C c = (C) a;
            c.g();
        } */

        // 但依旧可以通过反射调用被隐藏起来的方法:
        callHiddenMethod(a, "g");
        // 以及访问权限更小的方法:
        callHiddenMethod(a, "u");
        callHiddenMethod(a, "v");
        callHiddenMethod(a, "w");
    }

    static void callHiddenMethod(
            Object a, String methodName)
            throws Exception {
        Method g =
                a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a); // 将获得的方法g()重定位到a
    }
}

        程序执行的结果是:

        即使访问权限限制了使用者对类的直接访问,反射依旧提供了足以调用所有方法的能力,只需要我们知道类中方法的名字即可。

        并且,即使我们只发布代码的已编译版本,JDK自带的反编译器依旧可以展示出文件中所有的成员。JDK的反编译命令如下:

javap -private C

        执行上述命令,可得到如下结果:

通过这种方式,任何人都可以看到被隐藏的方法或签名(并调用它们)

        即使内部私有类也不能例外:

【例子:利用反射访问私有内部类】

package reflection;

import reflection.interfacea.A;

class InnerA {
    private static class C implements A {
        @Override
        public void f() {
            System.out.println("public C.f()");
        }

        public void g() {
            System.out.println("public C.g()");
        }

        void u() {
            System.out.println("package C.u()");
        }

        protected void v() {
            System.out.println("protected C.v()");
        }

        private void w() {
            System.out.println("private C.w()");
        }
    }

    public static A makeA() {
        return new C();
    }
}

public class InnerImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = InnerA.makeA();
        a.f();
        System.out.println(a.getClass().getName());

        // 通过反射访问私有类内部:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
}

        程序执行的结果是:

        匿名类也是如此(代码结构与上面的大致相同):

【例子:通过反射访问匿名类】

package reflection;

import reflection.interfacea.A;

class AnnoymousA {
    public static A makeA() {
        return new A() {
            @Override
            public void f() {
                System.out.println("public C.f()");
            }

            public void g() {
                System.out.println("public C.g()");
            }

            void u() {
                System.out.println("package C.u()");
            }

            protected void v() {
                System.out.println("protected C.v()");
            }

            private void w() {
                System.out.println("private C.w()");
            }
        };
    }
}

public class AnnoymousImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = AnnoymousA.makeA();
        a.f();
        System.out.println(a.getClass().getName());

        // 通过反射访问匿名类内部:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
}

        程序执行的结果是:

        除此之外,也可以通过反射访问字段:

【例子:通过反射访问字段】

package reflection;

import java.lang.reflect.Field;

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "这条语句是private final的";
    private String s2 = "这条语句是private的";

    @Override
    public String toString() {
        return "该类拥有的private字段如下:\n\t" +
                "i = " + i + "\n\t" +
                "s = " + s + "\n\t" +
                "s2 = " + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args)
            throws Exception {
        WithPrivateFinalField pf =
                new WithPrivateFinalField();
        System.out.println(pf);

        System.out.println("\n通过反射访问字段:");
        // Field类可用于反射字段
        Field field = pf.getClass().getDeclaredField("i");
        field.setAccessible(true); // 允许访问
        System.out.println("f.getInt(pf):"
                + field.getInt(pf));
        field.setInt(pf, 47);
        System.out.println("使用setInt()改变i的值," + pf);

        System.out.println("=====");
        field = pf.getClass().getDeclaredField("s");
        field.setAccessible(true); // 允许访问
        System.out.println("f.get(pf):"
                + field.get(pf));
        field.set(pf, "尝试改变s");
        System.out.println("使用set()无法改变s的值," + pf);

        System.out.println("=====");
        field = pf.getClass().getDeclaredField("s2");
        field.setAccessible(true); // 允许访问
        System.out.println("f.get(s2):"
                + field.get(pf));
        field.set(pf, "不安全");
        System.out.println("使用set()改变s的值," + pf);
    }
}

        程序执行的结果是:

        不过final字段还是安全的,不会因为反射而反射变化。

        一般而言,反射带来的麻烦不会有想象中的那么大,因为如果有人使用了反射,那么他们也应该承受代码改变带来的风险。并且,Java提供这样一个后门来访问类,确实可以解决一些问题。

    注意:面向对象编程语言要求,在任何可能的地方使用多态,而只在必要的地方使用反射(若一定要使用,可以将反射放到一个特定的类中进行使用。但我们也可能找到一个更好的替代方案)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/125101.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C/C++轻量级并发TCP服务器框架Zinx-游戏服务器开发005:守护进程与进程监控

文章目录 1 守护进程1.1 进程组和会话1.2 会话的相关概念1.3 守护进程的概念1.4 守护线程的特点1.5 守护进程创建的基本步骤1.6 本项目守护进程的实现 2 进程监控2.1 进程监控的实现 1 守护进程 1.1 进程组和会话 进程除了有进程的PID之外还有一个进程组,进程组是…

threejs (二) 相机

正交相机 const camera new THREE.OrthographicCamera(-aspect,aspect,aspect,-aspect,0.1, //进平面1000 //远平面); // 透视相机创建相机辅助线 const cameraHelper new THREE.CameraHelper(this.camera);创建一个透视相机观察正交相机 // 创建透视相机const watchCamera …

【算法与数据结构】39、LeetCode组合总和

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:这道题当中数字可以多次使用,那么我们在递归语句当中不能直接找下一个candidate的元素&…

两台linux虚拟机之间实现免密登录

主要实现两台虚拟机之间的免密登录,总所周知,虚拟机之间登录使用的协议是ssh协议,端口号是 22 主机 创建对应的加密文件 [rootweb-2 ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.s…

docker容器中运行jar 出现invalid or corrupt jarfile

1,背景: 在本地java开发完毕之后,想要打包成docker镜像,方便安装。由于本地没有docker环境,也懒得装了。有一台测试的linux机器可以使用,所以先在本地打包生成xxx.jar,然后拷贝到有docker环境的…

vite + electron引入itk报错

代码 import { readImageArrayBuffer } from itk-wasm console.log(readImageArrayBuffer)通过itk-wasm官网,创建新的项目vitevue(vue2或者vue3),都没问题。加入electeon后包此错。通过排查,意外找到原因,…

抵御数字威胁的铠甲——发现迅软DSE加密软件在企业保护中的关键角色

目前国内有自主知识产权和研发成果的企业,它们的电子文档大都以明文的方式存储在计算机硬盘中,电子格式存储的重要机密信息却由于传播的便利性和快捷性,对分发出去的文档无法控制,大的增加了管理的复杂程度,这部分信息…

Swift--量值与基本数据类型

系列文章目录 第一章: Swift–量值与基本数据类型 文章目录 系列文章目录前言对学习过程做一个记录 变量和常量命名规范注释 元祖类型可选类型拆包 typealias 前言 对学习过程做一个记录 提示:以下是本篇文章正文内容,下面案例可供参考 变量和常量 …

家用工作站方案:ThinkBook 14 2023 版

本篇文章聊聊今年双十一,我新购置的家用工作站设备:ThinkBook 14 2023,一台五千元价位,没有显卡的笔记本。我为什么选择它,它又能做些什么。 写在前面 2021 年年中的时候,我写过一篇《廉价的家用工作站方…

开源知识库软件xwiki在Windows下的安装

文章目录 开源知识库软件-xwiki在windows上的部署0、参考文档1、前置环境准备1.1、Windows版本及系统配置1.2、JDK11安装1.3、Tomcat9安装1.4、MySQL5.7数据库的安装 2、xwiki安装3、配置3.1、修改配置支持对文档内容进行搜索 4、问题解决4.1、附件无法上传问题4.1、附件无法下…

【309. 买卖股票的最佳时机含冷冻期】

目录 一、题目解析 二、算法原理 三、代码实现 class Solution { public:int maxProfit(vector<int>& prices) {int nprices.size();vector<vector<int>> dp(n,vector<int>(3));dp[0][0]-prices[0];dp[0][1]0;dp[0][2]0;for(int i1;i<n;i){dp…

Apipost发起请求,能正确返回,日志却打印java.io.EOFException: null 的原因

http响应头首部Content-Length - 程序员大本营 http响应头首部Content-Length HTTP Content-Length深入实践-CSDN博客 用了这么久HTTP, 你是否了解Content-Length?-CSDN博客 具体分析可看上面参考文章。 解决办法&#xff1a;可在请求头加上Content-Length&#xff0c;准确…

关于卷积神经网络的多通道

多通道输入 当输入的数据包含多个通道时&#xff0c;我们需要构造一个与输入通道数相同通道数的卷积核&#xff0c;从而能够和输入数据做卷积运算。 假设输入的形状为n∗n&#xff0c;通道数为ci​&#xff0c;卷积核的形状为f∗f&#xff0c;此时&#xff0c;每一个输入通道都…

通过一道题目带你深入了解WAF特性、PHP超级打印函数、ASCII码chr()对应表等原理[RoarCTF 2019]Easy Calc 1

题目环境&#xff1a; 依此输入以下内容并查看回显结果 11 1’ index.php ls 到这里没思路了 F12查看源代码 一定要仔细看啊&#xff0c;差点没找到&#xff0c;笑哭 访问calc.php文件 果然有点东西 PHP代码审计 error_reporting(0);关闭错误报告 通过GET方式传参的参数num sho…

ssm+vue的高校学生课堂考勤系统设计与实现(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的高校学生课堂考勤系统设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转…

开发知识点-NodeJs-npm/Pnpm/Vite/Yarn包管理器

包管理器 vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序npm 全局变量pnpmPnpm介绍ViteYarn ‘vue-cli-service’ 不是内部或外部命令&#xff0c;也不是可运行的程序 yarn yarn add vue-amap yarn add vue-amap ant-design-vue npm 全局变量 换主机 新…

AVL树的插入详解

AVL树 为什么有AVL树的出呢&#xff1f;其实我们用的map/multimap/set/multiset的底层都是二叉搜索树&#xff0c;但是二叉搜索树有一个很大的缺陷&#xff0c;就是当往树中插入的元素有序或者接近有序&#xff0c;二叉搜索树就会退化成单支树&#xff0c;时间复杂度会退化成…

计算机服务器中了locked勒索病毒怎么办,勒索病毒解密,数据恢复

随着网络技术的不断成熟&#xff0c;网络中存在的病毒威胁也不断增多&#xff0c;近期&#xff0c;云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计算机服务器数据库遭到了勒索病毒攻击&#xff0c;并且勒索病毒的攻击与加密形式也发生了许多变化。其中攻击次数较…

记一次 Android 周期性句柄泄漏的排查

滴滴国际化外卖 Android 商户端正常迭代版本过程中&#xff0c;新版本发布并且线上稳定一段时间后&#xff0c;突然触发线上 Crash 报警。 第一次排查发现是在依赖的底层平台 so 库中崩溃&#xff0c;经过沟通了解到其之前也存在过崩溃问题&#xff0c;所以升级相关底层 so 版本…

Linux mx6ull-驱动(1)hello

编写第一个驱动&#xff0c;hello_drv 一、获取内核、编译内核。 这里为什么要获取内核呢&#xff0c;因为我们写的是驱动程序&#xff0c;而不是裸机程序。也就是我们的板子已经烧入进去了uboot、内核&#xff0c;根文件。然后我们要在这个板子的内核的基础上&#xff0c;来…