反射,它就像是一种魔法,引入运行时自省能力,赋予了 Java 语言令人意外的活力,通过运行时操作元数据或对象,Java 可以灵活地操作运行时才能确定的信息
这里笔者就深入浅出总结下Java反射,若有不正确地方,感谢评论区指正交流~ 建议打开idea,写一个Java反射的demo,跟着调试,效果会更好 :)
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。有了反射,使Java相对于C、C++等语言就有了很强大的操作对象属性及其方法的能力,注意,反射与直接调用对象方法和属性相比,性能有一定的损耗,但是如果不是用在对性能有很强的场景下,反射都是一个很好且灵活的选择。
说到反射,首先要了解什么是Class。每个类都会产生一个对应的Class对象,一般保存在.class文件中。所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。类加载时,类加载器首先会检查这个类的Class对象是否已被加载过,如果尚未加载,默认的类加载器就会根据类名查找对应的.class文件。
class文件
任何一个Class文件都对应着唯一一个类或接口的信息(这里的类包括抽象类哈),但反过来,类或接口信息并不一定都定义在文件里(比如类或接口可能动态生成,Spring中AOP的实现中就有可能动态生成代理类)。Class文件是一组以8字节为基础单位的二进制文件,各个数据项严格按照顺序紧凑着排列,中间几乎没有任何分隔符,也就是说整个Class文件存储的几乎都是程序运行所需的必要数据。
想在运行时使用类型信息,必须获取对象(比如类Base对象)的Class对象的引用,使用Class.forName(“Base”)可以实现该目的,或者使用Base.class。注意,有一点很有趣,使用”.class”来创建Class对象的引用时,不会自动初始化该Class对应类,使用forName()会自动初始化该Class对应类。使用”.class”不会自动初始化是因为被延迟到了对静态方法(构造器隐私地是静态的)或者非常数静态域进行首次引用时才进行。
为了使用类而做的准备工作一般有以下3个步骤:
- 加载:由类加载器完成,找到对应的字节码,创建一个Class对象
 - 链接:验证类中的字节码,为静态域分配空间
 - 初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块
 
如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。而Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。
反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类。因此,那个类的.class对于JVM来说必须是可获取的,要么在本地机器上,要么从网络获取。所以对于RTTI和反射之间的真正区别只在于:
- RTTI:编译器在编译时打开和检查.class文件
 - 反射:运行时打开和检查.class文件
 
反射应用实践
反射获取对象中所有属性值:
public class Person { private String name; private int age; // ...} Person person = new Person(); person.setName("luo"); person.setAge(25); try { Class clazz = person.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); System.out.println(field.getType() + " | " + field.getName() + " = " + field.get(person)); } // 通过反射获取某一个方法 Method method = clazz.getMethod("setName", String.class); method.invoke(person, "bei"); } catch (Exception e) { e.printStackTrace(); }
平常的项目开发基本很少与反射打交道,因为框架已经帮我们做了很多的事情了。但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架,也是利用CGLIB 反射机制才得以实现。
反射技术在在框架和中间件技术应用较多,有一句老话就是反射是Java框架的基石。典型的使用就是Spring的IoC实现,不管对象谁管理创建,只要我能用就行。再比如RPC技术可以借助于反射来实现,本地主机将要远程调用的对象方法等信息发送给远程主机,这些信息包括class名、方法名、方法参数类型、方法入参等,远程主机接收到这些信息后就可以借助反射来获取并执行对象方法,然后将结果返回即可。
说了那么多,那么Java反射是如何实现的呢?简单来说Java反射就是靠JVM和Class相关类来实现的,Class相关类包括Field、Method和Constructor类等。类加载器加载完成一个类之后,会生成类对应的Class对象、Field对象、Method对象、Constructor对象,这些对象都保存在JVM(方法区)中,这也说明了反射必须在加载类之后进行的原因。使用反射时,其实就是与上述所说的这几个对象打交道呀(貌似Java反射也就这么一回事哈)。
既然了解了Java反射原理,可以试想一下C++为什么没有反射呢,想让C++拥有反射该如何做呢?Java相对于C++实现反射最重要的差别就是Java可以依靠JVM这一悍将,可以由JVM保存对象的相关信息,然后应用程序使用时直接从JVM中获取使用。但是C++编译后直接变成了机器码了,貌似类或者对象的啥信息都没了。。。 其实想让C++有用反射能力,就需要保存能够操作类方法、类构造方法、类属性的这些信息,这些信息要么由应用程序自己来做,要么由第三方工具来保存,然后应用程序使用从它那里获取,这些信息可以通过(函数)指针来记录,使用时通过指针来调用。
反射机制
这里我们以Method.invoke流程来分析反射流程:
public class Person { private String name; private int age; public String getName() { return name; }
