Java反射机制
Java反射机制

Java反射机制

Java反射机制是指在运行时动态地获取和操作类的信息的能力。通过反射,我们可以在程序运行期间动态地分析、修改和调用类的方法、构造函数和字段等。这使得我们可以在不直接访问源代码的情况下,对类进行操作和扩展。

反射的实现原理主要基于Java虚拟机(JVM)在加载类时所做的事情。当JVM加载一个类时,它会进行一系列的步骤,包括加载、链接(验证、准备、解析)和初始化。在这个过程中,JVM会为这个类创建一个Class对象,这个对象包含了类的所有元数据信息,如类名、父类、实现的接口、所有的成员(方法、字段、构造器等)以及注解等。

反射的实现过程大致如下:

  1. 获取Class字节码对象:这是使用反射的第一步,通过上面提到的三种方式之一获取到目标类的Class对象。
  2. 解析类的结构:通过Class对象提供的方法,可以获取到类的所有成员信息,包括构造器、方法和字段等。
  3. 创建对象:通过Class对象的 newInstance() 方法或者获取到的Constructor对象的 newInstance() 方法可以创建类的实例。
  4. 调用方法:通过获取到的Method对象的 invoke() 方法可以调用类的方法。
  5. 访问字段:通过获取到的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);
    }
}

输出结果为:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

index