参考视频链接: 哔哩哔哩视频.
能够分析类能力的程序叫做反射(reflective),对于任何一个Class类,反射可以在运行时直接得到这个类的全部成分,包括构造器,成员方法,成员变量。获得的构造器对象为Constructor,成员方法对象为Method,成员变量对象为Field。这种在运行时动态获取类信息以及动态调用类中成分的能力叫做Java语言的反射机制。
反射的关键,或者说第一步,是获取编译后的Class类对象。
对于Class类(java.lang.Class),定义了静态方法forName(String className)方法,获取Class类对象,className要写全限名,也就是包名+类名。假设我们在包com.reflective下创建了Student类,可以使用:
Class.forName("com.reflective.Student");
对于要反射的类,可以通过类名.class获得Class类对象。例如Student.class.
在运行时,可以通过对象.getClass()获得对象所属类的Class类对象,这个是Object类的方法,因此所有类都有这个方法。例如student.getClass().
在获得了Class类对象之后,通过调用Class类对象的方法可以获得其构造器对象Constructor。
获取Class类对象中的所有构造器对象,返回值为构造器数组,注意这个方法只能获取public修饰的构造器。
获取Class类对象中的所有构造器对象,返回值为构造器数组,即使是private修饰的构造器也能拿到!
获取Class类对象中的一个构造器,实参应该依次传入该构造器形参的Class类对象。举个例子:
Class class = student.getClass();
class.getConstructor(String.class, Integer.class);
获取Class类对象中的一个构造器,实参应该依次传入该构造器形参的Class类对象,即使是private修饰的构造器也能拿到。
获得构造器的作用是初始化一个对象返回。调用构造器的newInsrance方法,并且传入初始化参数(如果构造器需要的话),可以初始化对象并返回。需要注意的是如果是private修饰的构造器,即使获得了也不能直接newInstance,应该先调用构造器的setAccessible(true),才能初始化对象。
在获得了Class类对象后,可以调用Class类对象的方法获得成员变量,并对其赋值或者取值。
获取Class类对象的所有public成员变量对象的数组。
获取Class类对象的所有成员变量对象的数组,即使是private修饰的成员变量也能拿到。
获取Class类对象的单个public成员变量对象。传入参数为成员变量名字。
获取Class类对象的单个成员变量对象,即使是private修饰的成员变量也能拿到。传入参数为成员变量名字。
获得了Field对象后,调用它的set()方法可以为属性赋值,第一个参数传入目标对象,如student,第二个参数传入值;调用get方法可以获得值,传入目标对象。
调用Class类对象的getMethods()方法,能获得该类的所有public修饰的Method对象的数组。
调用Class类对象的getDeclaredMethods()方法,能获得该类的所有Method对象的数组,即使是private修饰也可以获取。
调用Class类对象的getMethod()方法,能获得该类的指定的由public修饰的Method对象,传入第一个参数为方法名,后面的参数为方法形参的Class类,例如String.class。
调用Class类对象的getDeclaredMethod()方法,能获得该类的指定的Method对象,传入第一个参数为方法名,后面的参数为方法形参的Class类,例如String.class。即使是private修饰也可以获取。
获得Method对象之后。调用它的invoke()方法可以触发该方法执行,传入第一个参数为已经实例化的对象,例如student,后面的参数为调用方法需要传递的参数。
如果想执行private修饰的方法,需要先调用Method对象的setAccessible(true)方法,再调用invoke()方法。
集合中如果加了泛型,则只能在该集合中添加这种类型的数据,例如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");
使用反射能够实现图中的需求,这个需求其实是通用框架的底层原理。如接受到一个对象后,不清楚成员变量有几个,成员变量名是什么,等。这些都可以通过上面的反射机制来解决,因此通过反射可以完成这些需求。