Java反射详解及作用
参考视频链接: 哔哩哔哩视频.
1. 反射概述
能够分析类能力的程序叫做反射(reflective),对于任何一个Class类,反射可以在运行时直接得到这个类的全部成分,包括构造器,成员方法,成员变量。获得的构造器对象为Constructor,成员方法对象为Method,成员变量对象为Field。这种在运行时动态获取类信息以及动态调用类中成分的能力叫做Java语言的反射机制。
2. 获取编译后的Class类对象
反射的关键,或者说第一步,是获取编译后的Class类对象。
2.1 通过java.lang.Class类获取
对于Class类(java.lang.Class),定义了静态方法forName(String className)方法,获取Class类对象,className要写全限名,也就是包名+类名。假设我们在包com.reflective下创建了Student类,可以使用:
Class.forName("com.reflective.Student");
2.2 通过目标类获取
对于要反射的类,可以通过类名.class获得Class类对象。例如Student.class.
2.3 通过目标对象获取
在运行时,可以通过对象.getClass()获得对象所属类的Class类对象,这个是Object类的方法,因此所有类都有这个方法。例如student.getClass().
3. 反射获取构造器对象
在获得了Class类对象之后,通过调用Class类对象的方法可以获得其构造器对象Constructor。
3.1 获得Constructor对象
3.1.1 getConstructors()
获取Class类对象中的所有构造器对象,返回值为构造器数组,注意这个方法只能获取public修饰的构造器。
3.1.2 getDeclaredConstructors()
获取Class类对象中的所有构造器对象,返回值为构造器数组,即使是private修饰的构造器也能拿到!
3.1.3 getConstructor()
获取Class类对象中的一个构造器,实参应该依次传入该构造器形参的Class类对象。举个例子:
Class class = student.getClass();
class.getConstructor(String.class, Integer.class);
3.1.4 getDeclaredConstructor()
获取Class类对象中的一个构造器,实参应该依次传入该构造器形参的Class类对象,即使是private修饰的构造器也能拿到。
3.2 通过构造器初始化对象
获得构造器的作用是初始化一个对象返回。调用构造器的newInsrance方法,并且传入初始化参数(如果构造器需要的话),可以初始化对象并返回。需要注意的是如果是private修饰的构造器,即使获得了也不能直接newInstance,应该先调用构造器的setAccessible(true),才能初始化对象。
4. 反射获取成员变量对象
在获得了Class类对象后,可以调用Class类对象的方法获得成员变量,并对其赋值或者取值。
4.1 获得Field对象
4.1.1 getFields()
获取Class类对象的所有public成员变量对象的数组。
4.1.2 getDeclaredFields()
获取Class类对象的所有成员变量对象的数组,即使是private修饰的成员变量也能拿到。
4.1.3 getField()
获取Class类对象的单个public成员变量对象。传入参数为成员变量名字。
4.1.4 getDeclaredField()
获取Class类对象的单个成员变量对象,即使是private修饰的成员变量也能拿到。传入参数为成员变量名字。
4.2 取值赋值
获得了Field对象后,调用它的set()方法可以为属性赋值,第一个参数传入目标对象,如student,第二个参数传入值;调用get方法可以获得值,传入目标对象。
5. 反射获取方法对象
5.1 获得Method对象
5.1.1 getMethods()
调用Class类对象的getMethods()方法,能获得该类的所有public修饰的Method对象的数组。
5.1.2 getDeclaredMethods()
调用Class类对象的getDeclaredMethods()方法,能获得该类的所有Method对象的数组,即使是private修饰也可以获取。
5.1.3 getMethod()
调用Class类对象的getMethod()方法,能获得该类的指定的由public修饰的Method对象,传入第一个参数为方法名,后面的参数为方法形参的Class类,例如String.class。
5.1.4 getDeclaredMethod()
调用Class类对象的getDeclaredMethod()方法,能获得该类的指定的Method对象,传入第一个参数为方法名,后面的参数为方法形参的Class类,例如String.class。即使是private修饰也可以获取。
5.2 触发该方法执行
获得Method对象之后。调用它的invoke()方法可以触发该方法执行,传入第一个参数为已经实例化的对象,例如student,后面的参数为调用方法需要传递的参数。
如果想执行private修饰的方法,需要先调用Method对象的setAccessible(true)方法,再调用invoke()方法。
6. 反射的作用
6.1 绕过编译阶段为集合添加数据
集合中如果加了泛型,则只能在该集合中添加这种类型的数据,例如ArrayList,便只能添加Integer类型的数据。泛型只是在编译阶段可以约束集合只能操作某种数据类型,在java文件编译成class文件进入运行阶段的时候,其真实类型都是ArrayList,泛型相当于被擦除了。反射是作用在运行时的技术,因此可以绕过编译阶段为集合添加数据。例如:
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(100);
list.add(200);
//会编译报错
//list.add("word");
Class c = list.getClass();
Method method = c.getDeclaredMethod("add",Object.class);
//list会添加word字符串,并且不会报错
method.invoke(list,"word");
6.2 通用框架的底层原理
使用反射能够实现图中的需求,这个需求其实是通用框架的底层原理。如接受到一个对象后,不清楚成员变量有几个,成员变量名是什么,等。这些都可以通过上面的反射机制来解决,因此通过反射可以完成这些需求。