Java反射机制是指在运行时动态地获取和操作类的信息的能力。通过反射,我们可以在程序运行期间动态地分析、修改和调用类的方法、构造函数和字段等。这使得我们可以在不直接访问源代码的情况下,对类进行操作和扩展。
反射的实现原理主要基于Java虚拟机(JVM)在加载类时所做的事情。当JVM加载一个类时,它会进行一系列的步骤,包括加载、链接(验证、准备、解析)和初始化。在这个过程中,JVM会为这个类创建一个Class对象,这个对象包含了类的所有元数据信息,如类名、父类、实现的接口、所有的成员(方法、字段、构造器等)以及注解等。
反射的实现过程大致如下:
- 获取Class字节码对象:这是使用反射的第一步,通过上面提到的三种方式之一获取到目标类的Class对象。
- 解析类的结构:通过Class对象提供的方法,可以获取到类的所有成员信息,包括构造器、方法和字段等。
- 创建对象:通过Class对象的
newInstance()
方法或者获取到的Constructor对象的newInstance()
方法可以创建类的实例。 - 调用方法:通过获取到的Method对象的
invoke()
方法可以调用类的方法。 - 访问字段:通过获取到的Field对象的
get()
或set()
方法可以读取或修改类的字段值。
获取字节码对象
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
获取字节码对象有三种方式:
- Class.forName(“全类名”);
- 类名.class
- 对象.getClass();
public class ReflectDemo1 {
/*
获取字节码对象的三种方式
*/
public static void main(String[] args) throws ClassNotFoundException {
// 1. 通过Class的静态方法forName
Class<?> class1 = Class.forName("com.wut.domain.Student");
// 2. 类名.class
Class<Student> class2 = Student.class;
// 3. Object类中的getClass()
Student student = new Student();
Class<? extends Student> class3 = student.getClass();
System.out.println(class1);
System.out.println(class2);
System.out.println(class3);
// 类的字节码对象唯一
System.out.println(class1 == class2);
System.out.println(class1 == class3);
System.out.println(class2 == class3);
}
}
由代码和运行结果可知,三种方法均可获取到字节码对象,且获取到的字节码对象唯一。
反射类中的构造方法(Constructor)
这里有几点注意事项:
- getConstructors() 只能返回所有访问修饰符为public的构造函数对象,而 getDeclaredConstructors() 暴力返回所有构造函数对象
- getConstructor() 和 getDeclaredConstructor() 都可以返回单个构造方法对象,若需要返回带参构造函数对象,这两个方法参数是参数对应数据类型类的字节码对象
- 在实际应用过程中,一般禁止暴力反射
Constructor类用于创建对象的方法(newInstance)
得到构造方法对象后,我们可以创建对象,使用的方法为:newInstance(Object…initargs)
例如,通过Constructor类的newInstance()方法,我们可以在运行时动态地创建一个类的实例。该方法相当于调用了类的无参构造函数,并返回一个新创建的对象。
Constructor<Student> constructor = Student.class.getConstructor();
Student stu = constructor.newInstance();
需要注意的是,由于 getConstructor() 方法只能返回公共构造方法对象,如果类没有定义无参构造函数或者是私有的,将会抛出 IllegalAccessException 异常。
但是可以通过 setAccessible(boolean flag) 方法设置为true,表示取消访问检查。
Constructor<Student> constructor = Student.class.getConstructor();
constructor.setAccessible(true);
Student stu = constructor.newInstance();
通过运行以上代码即可成功创建对象。
另外,创建对象时,获取的构造方法对象是有参的,那么创建对象也必须是有参的;获取的构造方法对象是无参的,那么创建对象也必须是无参的。如果不一致,则会抛出 IllegalArgumentException 异常。
反射类中的成员变量(Field)
对于返回成员变量对象数组的两个方法,由于是无参的,直接调用即可,非常简单。
而对于返回单个成员变量对象的两个方法,其参数是类中的成员变量的字符串。
一般在类中,我们都会将成员变量设为私有,所以这里也可以通过使用 setAccessible(boolean flag) 方法来设置访问权限。
// 1. 获取类的字节码对象
Class<Student> studentClass = Student.class;
// 2. 暴力反射内部的成员变量对象
Field ageField = studentClass.getDeclaredField("age");
Field nameField = studentClass.getDeclaredField("name");
// 3. 设置访问权限
ageField.setAccessible(true);
nameField.setAccessible(true);
Field类的设置和获取方法
- 对于 set 方法,第一个参数是对象,第二个参数是具体的值;
- 对于 get 方法,参数是对象
// 利用构造方法对象创建对象
Constructor<Student> constructor = studentClass.getConstructor();
Student stu1 = constructor.newInstance();
Student stu2 = constructor.newInstance();
// 4. 使用成员变量对象,完成赋值和获取操作
ageField.set(stu1, 23);
nameField.set(stu1, "张三");
ageField.set(stu2, 24);
nameField.set(stu2, "李四");
System.out.println(ageField.get(stu1));
System.out.println(nameField.get(stu1));
System.out.println(stu1);
System.out.println(stu2);
反射类中的成员方法(Method)
- 对于 getMethods 方法,会返回所有 public 的成员方法对象的数组,包括继承父类的。
- 对于 getDeclaredMethods 方法,会返回所有成员方法对象的数组,但不会含有继承父类的。
- 对于 getMethod 和 getDeclaredMethod 两个返回单个成员方法对象的方法,其第一个参数是方法名。如果成员方法无参,后面参数省略;如果成员方法含参,第二个及之后的参数是成员方法参数的字节码对象。
- 对于 invoke 方法,第一个参数是对象,第二个及之后的参数是传入的具体参数值。
例如,Student 类中含有两个 eat 成员方法:
public void eat(){
System.out.println("吃饭");
}
public void eat(int num){
System.out.println("吃"+num+"顿饭");
}
利用反射类获取 eat 成员方法并使用 invoke 运行方法:
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
// 1. 获取类的字节码对象
Class<Student> studentClass = Student.class;
// 2. 获取方法
Method eatMethod = studentClass.getMethod("eat", int.class);
System.out.println(eatMethod);
Constructor<Student> constructor = studentClass.getConstructor();
Student stu = constructor.newInstance();
eatMethod.invoke(stu, 10);
}
}
练习
需求:向一个泛型为Integer的集合中添加一个String类型的元素
思路:Java中的泛型是假的,只在编译时有效
public class ReflectTest1 {
public static void main(String[] args) throws Exception{
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4);
Class<? extends ArrayList> listClass = list.getClass();
listClass.getMethod("add", Object.class).invoke(list, "hello");
System.out.println(list);
}
}
输出结果为: