已复制
全屏展示
复制代码

Java 反射原理与实际使用场景


· 5 min read

反射就是 Reflection,Java 的反射是指程序在运行期可以拿到一个对象的所有信息。

一. 类的加载

类加载流程

加载的流程

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。Java的所有类都是一个叫做Class的类的一个实例,Class类大概如下所示。每加载一种classJVM就为其创建一个Class类型的实例,并关联起来。注意:这里的Class类型是一个名叫Classclass

public final class Class {
    private Class() {}
}

注意JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。

类加载举例

String类为例,当JVM加载String类时,它首先读取String.class文件(String.java编译而来)到内存,然后,为String类创建一个Class实例并关联起来,如下。这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。所以,JVM持有的每个Class实例都指向一个数据类型(classinterface):

Class cls = new Class(String);

二. 获取class方法

JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。如何获取一个classClass实例?有三个方法:

  • 方法一:直接通过一个class的静态变量class获取:Class<?> cls = String.class;
  • 方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:Class<?> cls = "Hello".getClass();
  • 方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:Class<?> cls = Class.forName("java.lang.String");
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> cls1 = String.class;
        Class<?> cls2 = "Hello".getClass();
        Class<?> cls3 = Class.forName("java.lang.String");

        System.out.println("cls1.equals(cls2) = " + cls1.equals(cls2));  // true
        System.out.println("cls2.equals(cls3) = " + cls2.equals(cls3));  // true
    }
}

三. 反射场景

  • 测试类
package org.example;

class Person {
    protected String name;
    protected int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

class Student extends Person {
    public int score;

    public Student() {
    }

    public Student(String name, int age, int score) {
        super(name, age);
        this.score = score;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "name:" + this.name + " " +
                "age:" + this.age + " " +
                "score:" + this.score;
    }
}
  • 常用方法
public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("org.example.Student");

        Method[] methods1 = cls.getDeclaredMethods();  // 获取 class 对象的所有方法(不包括父类)
        Method[] methods2 = cls.getMethods();          // 获取 class 对象的public方法(包括父类)

        Field[] fields1 = cls.getDeclaredFields();     // 获取 class 对象的所有属性(不包括父类)
        Field[] fields2 = cls.getFields();             // 获取 class 对象的public属性(包括父类)

        Class<?> superclass = cls.getSuperclass();     // 获取 class 对象的父类
        Class<?>[] interfaces = cls.getInterfaces();   // 获取 class 对象的所有接口

        Constructor<?>[] constructors1 = cls.getDeclaredConstructors();  // 获取 class 对象的所有声明构造函数
        Constructor<?>[] constructors2 = cls.getConstructors();          // 获取 class 对象public构造函数

        Constructor<?> constructor1 = cls.getDeclaredConstructor(        // 获取指定声明构造函数
                String.class, int.class, int.class);
        Constructor<?> constructor2 = cls.getConstructor(                // 获取指定声明的 public 构造函数
                String.class, int.class, int.class);

        Annotation[] annotations = cls.getAnnotations();                 // 获取 class 对象的所有注解
        Annotation annotation = cls.getAnnotation(Deprecated.class);     // 获取 class 对象指定注解

        Type type = cls.getGenericSuperclass();                          // 获取 class 对象的直接父类的 Type
        Type[] types = cls.getGenericInterfaces();                       // 获取 class 对象的所有接口的 Type 集合

        Object zhang = constructor1.newInstance("zhang", 20, 98);        // 初始化实例
        Field scoreField = cls.getField("score");                        // 获取某个 public 字段
        scoreField.set(zhang, 90);                                       // 设置某个 public 字段的值
        Method setAgeMethod = cls.getMethod("setAge", int.class);        // 获取某个 public 方法
        setAgeMethod.invoke(zhang, 30);                                  // 调用某个 public 方法

    }
}

实际场景1

编码阶段不知道需要实例化的类名是哪个,需要在 runtime 从配置文件中加载

Class<?> cls = Class.forName("org.example.Student");

实际场景2

在 runtime 阶段,需要临时访问类的某个私有属性

package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

class Person {
    private int age;

    public Person(int age) {
        this.age = age;
    }
    private void setAge(int age) {
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("org.example.Person");
        Constructor<?> declaredConstructor = cls.getDeclaredConstructor(int.class);
        Object zhang = declaredConstructor.newInstance(18);

        // 属性赋值(私有、公有)
        Field ageField = cls.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(zhang, 19);
        System.out.println(ageField.get(zhang));

        // 调用方法(私有、公有)
        Method setAge = cls.getDeclaredMethod("setAge", int.class);
        setAge.setAccessible(true);
        setAge.invoke(zhang, 22);
        System.out.println(ageField.get(zhang));
    }
}

四. Trick奇技淫巧

  • 强行设置 Integer 的值
package org.example;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;


public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        Integer i = 10;

        Field field = Integer.class.getDeclaredField("value");
        
        // 这里要获取 Field 类的 modifiers 字段进行修改
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);

        // 去除 final 标记
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.setAccessible(true);

        // 强行设置值
        field.set(i, 100);
        System.out.println(i);
    }
}
  • 强制修改 list 的 size
package org.example;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;


public class Main {
    public static void main(String[] args) throws ReflectiveOperationException {
        List<String> list = new ArrayList<>();
        
        Field field = ArrayList.class.getDeclaredField("size");
        field.setAccessible(true);
        field.set(list, 10);              // 强制修改size的大小为 10
        
        list.add("test");                 // 只添加一个元素
        System.out.println(list.size());  // 大小直接变成 11
    }
}
🔗

文章推荐