AI 摘要

文章系统梳理 Java 反射机制:先对比编译期确定与运行期探查两种模式,阐明反射可在运行时获取类名、包、父类、接口、修饰符等元数据;继而演示通过 Class、Constructor、Field、Method 三类 API 获取构造器、字段、方法,并借助 setAccessible 突破访问限制,完成实例化、属性读写及方法调用;最后总结反射在框架解耦与动态扩展中的价值,同时指出其性能损耗、封装破坏与可读性下降等风险。

反射概述

反射简介

在正常的 Java 编程中,通常会实例化一个类来创建一个对象并调用其方法。

public class Main {

    public static void main(String[] args) {
        // 实例化Person对象
        Person person = new Person();

        // 调用Person对象的方法
        person.sayHello();
    }

}

这里在程序编译时就知道要创建 Person 类的对象,并且直接通过 new 关键字来创建,然后直接调用 sayHello 方法,这种在编译时就确定了类和操作的方式就是非反射的编程方式。

反射(Reflection)机制是 Java 语言的特性之一,在编译时不需要知道类是否存在,而在运行时再加载、探知、使用那些编译时完全未知的类。

Java 反射机制主要提供了以下的一些功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的属性和方法。
  • 在运行时调用任意一个对象的方法。

Java 反射 API

Java 反射技术的基本 API:

  • java.lang.Class 类可获取类和类的成员信息。
  • java.lang.reflect.Constructor 类可调用类的构造方法。
  • java.lang.reflect.Field 类可访问类的属性。
  • java.lang.reflect.Method 类可调用类的方法。

反射应用

使用反射的基本步骤

在 Java 程序中使用反射的基本步骤:

  • 导入 java.lang.reflect.* 包。
  • 获得需要操作的类的 java.lang.Class 对象。
  • 调用 Class 的方法获取 Field、Method 等对象。
  • 使用反射 API 操作实例成员。

获取 Class 实例

一个类或接口被加载后,从系统中都能获得一个代表该类或接口的 Class 类型的实例,其包含了关于该类或接口的各种信息,如类的名称、成员变量、方法、父类、实现的接口等。

Class 类是 Java 反射机制的起源和入口。

在 Java 程序中获得 Class 实例的方式:

  • 调用对象的 getClass 方法。
  • 调用类或接口的 class 属性。
  • 调用 Class.forName 方法。

在获得了某个类型对应的 Class 实例后,就可以调用 Class 实例的方法来获得该类型的信息,如构造函数(Constructor)、属性(Field)、方法(Method)等信息。


示例:获取 Class 实例

用户类(cn.duozai.entity.Person):

public class Person implements Serializable {

    /**
     * 私有属性-姓名
     */
    private String name;

    /**
     * 静态属性-年龄
     */
    static final int age = 30;

    /**
     * 保护属性-地址
     */
    protected String address;

    /**
     * 公有属性-消息
     */
    public String message;

    /**
     * 公有方法-getter
     *
     * @return java.lang.String
     */
    public String getName() {
        return name;
    }

    /**
     * 公有方法-setter
     *
     * @param name
     * @return void
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 静态方法-getter
     */
    static int getAge() {
        return age;
    }

    /**
     * 保护方法-getter
     */
    protected String getAddress() {
        return address;
    }

    /**
     * 私有方法-说悄悄话
     */
    private void silentMethod() {
        System.out.println("这是悄悄话");
    }

    /**
     * 公有方法-无参构造
     */
    public Person() {}

    /**
     * 私有方法-有参构造
     */
    private Person(String name) {
        this.name = name;
    }

    /**
     * 保护方法-有参构造
     */
    protected Person(String name, String address, String message) {
        this.name = name;
        this.address = address;
        this.message = message;
    }

    /**
     * 公有方法-toString
     */
    @Override
    public String toString() {
        return "{name:" + name + ", age:" + age + ", address:" + address
                + ", message:" + message + "}";
    }

}

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws ClassNotFoundException {
        // 方法1:调用对象的getClass方法
        Person person = new Person();
        Class clazz1 = person.getClass();

        // 方法2:调用类或接口的 class 属性
        Class clazz2 = Person.class;

        // 方法3:调用 Class.forName 方法
        Class clazz3 = Class.forName("cn.duozai.entity.Person");

        // 判断是否相等
        System.out.println("clazz1==clazz2 = " + (clazz1 == clazz2));
        System.out.println("clazz2==clazz3 = " + (clazz2 == clazz3));
    }

}

示例效果:


从 Class 实例获取基本信息

通过 Class 实例获取对应类型的基本信息的基本方法:

名称描述
String getName()以字符串的形式返回该类型的名称(包 + 类名)
String getSimpleName()以字符串的形式返回该类型的简称(类名)
Package getPackage()获取该类型所在的包
Class getSuperClass()返回该类型的父类的 Class 实例
Class[] getInterfaces()返回该类型所实现的全部接口的 Class 实例
int getModifiers()返回该类型的所有修饰符
其返回值由 public、protected、private 等对应的 int 常量组成
返回值应使用 Modifier 工具类来解码,才能判断修饰符的构成
Class[] getDeclaredClasses()返回该类型中包含的全部内部类的 Class 实例
class getDeclaringClass()返回该类型所在的外部类的 Class 实例

示例:从 Class 实例获取基本信息

基类(cn.duozai.entity.BaseClass):

public class BaseClass {}

用户类(cn.duozai.entity.Person):

/**
 * 用户类继承基类BaseClass,并实现序列化接口Serializable
 */
public class Person implements Serializable {

    // ...

}

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws ClassNotFoundException {
        // 获取Person用户类的Class实例
        Class clz = Class.forName("cn.duozai.entity.Person");

        // 获取类的全名
        System.out.println("类的名称:" + clz.getName());

        // 获取类的简称
        System.out.println("类的简称:" + clz.getSimpleName());

        // 获取类所在的包
        Package pkg = clz.getPackage();
        System.out.println("类所在的包:" + pkg.getName());

        // 获取父类
        Class superClz = clz.getSuperclass();
        System.out.println("父类的名称:" + superClz.getName());

        // 获取类实现的所有接口
        Class[] interfaces = clz.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println("类实现的接口:" + anInterface.getName());
        }

        // 获取类的修饰符
        System.out.println("类的修饰符(解码前):" + clz.getModifiers());
        System.out.println("类的修饰符(解码后):" + Modifier.toString(clz.getModifiers()));
    }

}

示例效果:


从 Class 实例获取构造函数

通过 Class 实例获取对应类型的所含构造函数的基本方法:

名称描述
Constructor getConstructor(Class...params)返回该类型指定参数列表的 public 公有的构造方法
构造方法的参数列表与 params 所指定的类型列表所匹配
Constructor[] getConstructors()返回该类型的所有 public 公有的构造方法
Constructor getDeclaredConstructor(Class...params)返回该类型的指定参数列表的构造方法,访问级别不限
Constructor[] getDeclaredConstructors()返回该类型的所有构造方法,访问级别不限

示例:从 Class 实例获取构造函数

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws ClassNotFoundException {
        // ...

        // 获取类的所有的构造函数
        Constructor[] allConstructors = clz.getDeclaredConstructors();
        // 遍历所有的构造函数
        for (Constructor aConstructor : allConstructors) {
            System.out.println("类的构造函数:");
            System.out.println("构造函数的名称:" + aConstructor.getName());
            System.out.println("构造函数的参数:" + Arrays.toString(aConstructor.getParameterTypes()));
            System.out.println("构造函数的修饰符:" + Modifier.toString(aConstructor.getModifiers()));
        }
    }

}

示例效果:


从 Class 实例获取所含属性

通过 Class 实例获取对应类型的所含属性的基本方法:

名称描述
Field getField(String name)返回该类型中指定名称的 public 公有的属性
name:指定属性名称
Field[] getFields()返回该类型中所有 public 公有的属性
Field getDeclaredField(String name)返回该类型中指定名称的属性,访问级别不限
Field[] getDeclaredFields()返回该类型中的全部属性,访问级别不限

示例:从 Class 实例获取所含属性

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws ClassNotFoundException {
        // ...

        // 获取类的所有的属性
        Field[] allFields = clz.getDeclaredFields();
        for (Field field : allFields) {
            System.out.println("类的属性:");
            System.out.println("属性的名称:" + field.getName());
            System.out.println("属性的类型:" + field.getType().getName());
            System.out.println("属性的修饰符:" + Modifier.toString(field.getModifiers()));
        }
    }

}

示例效果:


从 Class 实例获取所含方法

通过 Class 实例获取对应类型的所含方法的基本方法:

名称描述
Method getMethod(String name, Class... params)返回该实例中指定的 public 公有的方法
name:指定方法名称
params:指定参数列表
Method[] getMethods()返回该实例中所有 public 公有的方法
Method getDeclaredMethod(String name, Class... params)返回该实例中指定的方法,访问级别不限
Method[] getDeclaredMethods()返回该实例中的全部方法,访问级别不限

示例:从 Class 实例获取所含方法

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws ClassNotFoundException {
        // ...

        // 获取类的所有的方法
        Method[] allMethods = clz.getDeclaredMethods();
        for (Method method : allMethods) {
            System.out.println("类的方法:");
            System.out.println("方法的名称:" + method.getName());
            System.out.println("方法的返回值:" + method.getReturnType().getName());
            System.out.println("方法的修饰符:" + Modifier.toString(method.getModifiers()));
            System.out.println("方法的异常:" + Arrays.toString(method.getExceptionTypes()));
        }
    }

}

示例效果:


创建类的实例

通过反射来创建 Java 类型的实例的方式:

  • 使用 Class 实例的 newInstance 方法创建相关类型的实例。
  • 使用 Constructor 实例创建相关类型的实例。

应用实例:创建类的实例

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws Exception {
        // 获取Person用户类的Class实例
        Class clz = Class.forName("cn.duozai.entity.Person");

        // new的方式实例化Person对象
        Person person1 = new Person();
        System.out.println("person1 = " + person1);

        // 使用Class实例的newInstance方法实例化Person对象
        Object person2 = clz.newInstance();
        System.out.println("person2 = " + person1);

        // 使用Constructor实例创建Person对象
        // 通过Class实例获取指定的构造函数
        Constructor constructor1 = clz.getDeclaredConstructor(String.class);
        // 允许访问私有构造函数 private Person(String name){...}
        constructor1.setAccessible(true);
        // 通过构造函数创建实例
        Object person3 = constructor1.newInstance("多仔");
        System.out.println("person3 = " + person3);
    }

}

示例效果:


访问类的属性

使用 Field 实例可以对属性进行取值或赋值操作。

Field 实例访问属性的基本方法:

名称描述
xxx getXxx(Object obj)以 xxx 类型返回 obj 中相关属性的值
xxx:8 种基本数据类型之一
若 Field 实例表示的是一个静态属性,则 obj 可以设置为 null
Object get(Object obj)以 Object 类型返回 obj 中相关属性的值
void setXxx(Object obj, xxx val)将 obj 中相关属性的值设置为 val
xxx:8 种基本数据类型之一
void set(Object obj, Object val)将 obj 中相关属性的值设置为 val
void setAccessible(boolean flag)对相关属性设置访问权限
true:禁止 Java 语言访问检查

示例:访问类的属性

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws Exception {
        // 获取Person用户类的Class实例
        Class clz = Class.forName("cn.duozai.entity.Person");

        // 使用Class实例的newInstance方法实例化Person对象
        Object person = clz.newInstance();

        // 获取Person对象的name属性
        Field nameField = clz.getDeclaredField("name");
        // 设置name属性的访问权限为true
        nameField.setAccessible(true);

        // 获取Person对象的name属性
        System.out.println("修改前的name:" + nameField.get(person));

        // 修改Person对象的name属性的值
        nameField.set(person, "多仔");
        System.out.println("修改后的name:" + nameField.get(person));

        // 获取Person对象的age属性的值
        Field ageField = clz.getDeclaredField("age");
        ageField.setAccessible(true);
        // 静态属性可以通过get(null)获取,不需要传递具体的对象
        System.out.println("age:" + ageField.get(null));
    }

}

示例效果:


调用类的方法

Method 类中包含一个 invoke 方法,通过该方法可以调用 Java 类的实例方法和静态方法。

invoke 方法的基本用法:

Object invoke(Object obj, Object...args);

invoke 方法的基本参数:

名称描述
obj执行该方法的对象
若 Method 实例表示的是一个静态实例,则 obj 可以为 null
args执行该方法时传入的参数

示例:调用类的方法

测试类(cn.duozai.TestMain):

public class TestMain {

    public static void main(String[] args) throws Exception {
        // 获取Person用户类的Class实例
        Class clz = Class.forName("cn.duozai.entity.Person");

        // 使用Class实例的newInstance方法实例化Person对象
        Object person = clz.newInstance();

        // 获取Person对象的silentMethod方法
        Method method = clz.getDeclaredMethod("silentMethod");
        // 设置silentMethod方法为可访问
        method.setAccessible(true);
        // 调用Person对象的silentMethod方法
        method.invoke(person);
    }

}

示例效果:


反射总结

反射的优缺点

Java 反射机制允许程序创建和控制任何类的对象,无需提前硬编码目标类,提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力。

反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,如 MyBatis、Spring 等开源框架中都有大量运用,但反射会模糊程序内部逻辑,可读性较差,且反射可能会破坏封装,实际上只有当程序需要动态创建类的实例时才会考虑使用反射。