什么是反射?
反射简单来说,就是在代码运行期间,通过动态指定任意一个类,从而构建对象,并了解该类的成员变量和方法,甚至可以调用任意一个对象的属性和方法。以String对象为例子,传统构造方式和反射的实现方式如下:
//new 构造方法
String s = new String();
//反射
Class<?> forName = Class.forName("java.lang.String");
从代码中不难看到,第一种通过构造函数构造出来的对象,是在编译前就确定好了的。由此编译程序可以明确的知道当前操作的类是哪个,包含哪些属性、有什么方法。
然而对于第二种实现方式来说,通过反射映射出来的对象完全取决于你给定的字符串是什么。如果我们将这个字符串抽取成配置,那么生产出来的对象自然就会是动态的了。
为什么需要反射?
那么,你可能想问了,好好的new 对象不好吗?为啥还要设计出来一个反射呀?他有什么好处呢?其实,直白来讲,反射牺牲掉了安全、效率,更多的是为了提供更多的灵活性。
假设当前你所负责的代码,要处理两个不同的对象,那么为了完成工作,你可能需要通过多次的if-else判断、处理,哪怕两个对象内的字段名称、类型都是一样的,受限于编译器的限制,你也没法共用同一段代码。
if (i==1){
// ....执行针对ObjectA的处理方法
ObjectA o = (ObjectA) input;
o.setData(xxx);
} else if (i==2) {
// ....执行针对ObjectB的处理方法
ObjectB o = (ObjectB) input;
o.setData(xxx);
}
但是如果借助于反射,我们就可以将代码充分利用起来,从而最大程度减少代码开发,提升日常效率。
if (i==1){
// ....执行针对ObjectA的处理方法
Class<?> forName = Class.forName("com.example.demo.base.dto.ObjectA");
} else if (i==2) {
// ....执行针对ObjectB的处理方法
Class<?> forName = Class.forName("com.example.demo.base.dto.ObjectB");
}
Object o = (Object) input;
//同一段处理逻辑,无需再写两次
Field resultStr = forName.getDeclaredField("resultStr");
resultStr.set(o, "1");
在日常代码开发中,也有很多组件无形之中使用到了反射的设计原理。如我们熟悉的Bean拷贝组件BeanUtils,其方便的实现对象间的拷贝逻辑就是基于反射实现的。
如何实现反射?
首先我们知道,我们日常所编写的代码,最后都是会变成.java格式的文档。而.java格式的文档是不可能被机器直接执行的,因为机器只能够看懂二进制的文件。
因此,从.java文件到jvm中可执行的对象,它其实经过了如下的过程:
首先,我们的.java文件会被编译器编译成.class文件,对于每个.class文件,又会被JVM调用类加载的方式,加载到JVM内存中并生成为一个Class对象。这里我们简单看一下Class对象长什么模样:
可以看到Class类对象中包含了许多用于描述类的属性,如类名、所使用的类加载器等。包括通过Class类对象也可以搜索到当前类的构造器、特定的属性域等。
而且对于这个特殊的类来说,我们是不能够对其进行初始化的。可以看到,它只提供了一个私有的构造方法,且需要传入的是类加载器。而且在方法的注释上,也很明确的标注出来,这个构造方法只是提供给JVM创建实例用的,不应该作为默认的类构造器。
通常来说,如果我们需要调用一个Class对象,那么通常只能通过以下的方式:
Class<?> clz = Class.forName("xxx.class");
Class<?> clz = Object.class;
Class<?> clz = Object.getClass();
而且通过不断的往下搜索,我们也可以看到,这个方法的底层实现是通过native方法实现的。(部分java代码通过c等语言实现的,此时会被标记为native方法,是不可以直接调用的。)
虽然我们不能调用,但是本质上这个方法就是通过类加载器获取的过程。了解了这些之后,我们再将咱们的日常使用java类的过程梳理一下:
所以可以看到,反射本质上,维护实例的流程同正常的new创建对象并无过多的差异,他们本质都是基于class对象生成的实例,只是通过new的方式,用户是能够明确知道所需要使用的类的,因此编译器也能方便的对代码进行检查。
而使用反射的方式,则是利用了Class对象,创造出了编码的灵活性。本质思想就是:“你可以是任何类,但我的逻辑对任何类都适配。”通过这样的牺牲掉一些安全性,从而获得编码的灵活性。