java之反射原理

一般形如这样的代码:Foo f = new Foo();
所在的类文件首先被编译,度过编译期,然后是类加载器加载字节码文件,进入jvm进行运行,称为运行时。

在实例化对象后,外部调用public方法,程序正常运行,但在调用private方法时,jvm识别到方法为private方法,就禁止访问,出现编译错误,无法编译通过。

如下代码中,我想在外部调用Base类的x属性,只能通过提供的public的getter方法:

Class Base {
    public String t = "hello world";
    private String x = "something";
    public String getX() {
        return "hello";
    }
}

而反射的存在,为这样的非法访问提供了后门,我们可以在运行时动态加载类,进行private和public的改变,以此绕过jvm编译时检查。

可使用如下方式使用反射:

Class c = Class.forName("org.xxx.xxx.Base");
Method method = c.getDeclareMethod("get");
method.setAccessible(true);
Base b = (Base) c.newInstance();
print(method.invoke(b));//打印get()的返回值

当然Class.forName("class绝对路径")为获取Class的一种方式,另有:

Class c1 = Base.class;

Class c2 = new Base().getClass();

ClassLoader classLoader = this.getClass().getClassLoader();
Class c3 = classLoader.loaderClass("class绝对路径")

本例中使用的方式与ClassLoader很相似,都是通过类的绝对路径加载类,然后获取Class信息。

反射的动态创建,动态调用的关键就在于此,所谓相对于反射的静态调用,静态使用,就是在编译期间,先是将Base b = new Base()中的等号前的引用值压入jvm栈中,在运行时new Base()在jvm堆中分配空间,以此让jvm栈中的b指向刚在堆中分配的Base变量空间。也即是此类的实例化步骤(会在运行期实例化的类)早在编译期就确定了,类加载器也肯定在运行前就加载了Base类的字节码文件。

而反射却并不是这样,反射也是执行相应的代码,在编译期编译,但jvm并不知道反射操作要在运行期干什么,它看不懂Class.forName("xxxx")这样的操作的目的性,只是能检测这样的代码语法是合法的,而具体的操作还要等运行期去动态的加载xxx绝对路径下的类文件,然后生成Class信息,最后依据Class信息来生成实例类,更改private的操作得以执行,因为此时已经骗过了编译期检查,可以通过反射支持的行为,来“为所欲为”的修改你的类信息。


ps:多态真正的原理

Class Base {
    public String t = "hello world";
    private String x = "something";
    public String getX() {
        return "hello";
    }
}

写一个继承于Foo的类

Class Foo extends Base {
    
}
class Main {
    public static void main(String[] args) {
        Base b = new Base();
    }
}

假如将上诉代码改为:
Base b = new Foo();
b在编译期确定b.getClass()的值为Base,但是在运行时b.getClass()的值会因为new Foo()分配内存,然后将类型值赋值给b,b就被向上转型(多态)转换为Foo类型。

你可能感兴趣的:(java之反射原理)