一般形如这样的代码: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类型。