一、反射的概念
Java反射机制是指:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
反射提供了一种更加灵活的方式创建对象,得到对象的信息。如Spring IoC、Spring AOP、动态代理等,都是基于反射的。
反射的理解:
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作:
Phone phone = new Phone();
phone.setPrice(4);
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用:
Class clz = Class.forName("com.xxp.reflect.Phone");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Phone),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.xxp.reflect.Phone)。
所以说什么是反射?反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
反射的使用场景:
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。
反射的好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解耦,提高程序的可扩展性。
反射的应用:
可以利用反射的原理来设计框架,如果我们希望仅修改配置文件就可以实现创建类的对象并执行相应的方法,可以通过反射来实现。例如:Spring框架。
二、反射的原理
要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运行时识别对象和类的信息,主要有两种方式:一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。
RTTI要求是编译期对类是已知的,是因为其编译时要打开和检查Class文件,也就是Class.forName("name")。name是必须存在在当前classpath路径的class文件的文件名,也就是在编译时,编译器必须知道所有通过RTTI来处理的类。而当Class文件在编译时不可用时,就必须依赖反射机制在运行时打开和检查Class文件。
无论RTTI还是反射,使用的前提条件:必须先获得Class对象。
首先了解一下类的加载过程:
类的加载过程中,Java代码经历了三个阶段,而每个阶段,我们都可以根据该阶段的特点去获取相应的Class对象。
Java代码经历了三个阶段:
- Source 源代码阶段:此时字节码文件(Class文件)并没有加载进内存,我们必须进行手动加载去生成一个Class对象,因此我们需要全类名的方式(包名+类名)生成Class对象,此方法有可能抛出 ClassNotFoundException 异常;
- Class 类对象阶段:此时的字节码文件已经加载到了内存中,于是便有了类名,因此,我们只需要通过类名的class属性即可获取Class对象;
- Runtime 运行时阶段:此时已经创建了对象,于是我们就可以使用对象的方法来获取Class对象。
综上,获取Class对象的三种方式:
(1)Class.forName("全类名"):将字节码文件加载进内存,返回Class对象;
使用场景:多用于配置文件,将类名定义在配置文件中,读取文件并加载类。例如:JDBC加载驱动程序
(2)类名.class:通过类名的属性class获取;
使用场景:多用于参数的传递
(3)对象.getClass():getClass()方法在Object类中定义。
使用场景:多用于对象获取字节码的方式
注意:同一个字节码文件(*.class)在一次程序的运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
三、反射常用API
1. Class 对象
- 获取成员变量们:
* Field[] getFields():获取所有public修饰的成员变量
* Field getField(String name):获取指定名称的public修饰的成员变量
* Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name):获取指定名称的成员变量
- 获取构造方法们:
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
- 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
- 获取全类名:
* String getName()
2. Field 成员变量
- 设置值:
* void set(Object obj, Object value)
- 获取值:
* get(Object obj)
- 忽略访问权限修饰符的安全检查:
* setAccessible(true):暴力反射
3. Constructor 构造方法
- 创建对象:
* T newInstance(Object... initargs)
注:如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
4. Method 方法对象
- 执行方法:
* Object invoke(Object obj, Object... args)
- 获取方法名称:
* String getName:获取方法名
四、反射总结
new对象和反射得到对象的区别
- 使用new的时候,这个类可以没有被加载,也可以已经被加载;而在使用反射的时候,必须确保这个类已经加载并连接。
- new关键字可以调用任何public构造方法;而反射只能调用无参构造方法。
- new关键字是强类型的,效率相对较高;而反射是弱类型的,效率低。