Java 基础反射(Reflection)详解与实战案例

一、反射概述

反射(Reflection)是 Java 语言的一种高级特性,它允许程序在运行时获取类的信息(如类名、属性、方法、构造器等),并能动态操作类的成员(如创建对象、调用方法、修改属性等)。

反射的核心是 java.lang.Class 类和 java.lang.reflect 包下的 API(如 ConstructorFieldMethod 等)。通过这些 API,我们可以突破编译期的限制,实现动态编程。

为什么需要反射?

  • 框架开发的基础(如 Spring 的 IOC、MyBatis 的 mapper 映射);
  • 动态调用类的方法或修改属性,提升代码灵活性;
  • 适配不同版本的类或第三方库,增强程序扩展性。

二、反射核心 API

类 / 接口 作用 常用方法举例
Class 代表类的字节码对象,反射的入口 getFields()getMethods()
Constructor 代表类的构造方法 newInstance()getParameterTypes()
Field 代表类的成员变量 get()set()setAccessible()
Method 代表类的成员方法 invoke()getReturnType()

三、反射实战案例

3.1 准备工作:定义示例类

为了演示反射的使用,我们先定义一个 User 类作为操作目标:

package com.example.reflection;

import java.io.Serializable;

/**
 * 用户类(用于反射演示)
 */
public class User implements Serializable {
    // 私有属性
    private String username;
    private int age;
    // 公有属性
    public String address;

    // 无参构造
    public User() {
    }

    // 有参构造
    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    // 私有构造
    private User(String username) {
        this.username = username;
    }

    // 普通方法
    public String getUsername() {
        return username;
    }

    private void setUsername(String username) {
        this.username = username;
    }

    // 静态方法
    public static void printInfo() {
        System.out.println("这是 User 类的静态方法");
    }

    @Override
    public String toString() {
        return "User{username='" + username + "', age=" + age + ", address='" + address + "'}";
    }
}

3.2 案例 1:获取 Class 对象(反射的入口)

Class 对象是反射的基础,获取 Class 对象有 3 种方式:

public class ReflectionDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式 1:通过对象的 getClass() 方法(需先创建对象)
        User user = new User();
        Class<?> clazz1 = user.getClass();
        System.out.println("方式 1:" + clazz1.getName()); // 输出:com.example.reflection.User

        // 方式 2:通过 类名.class(编译期确定,无需创建对象)
        Class<?> clazz2 = User.class;
        System.out.println("方式 2:" + clazz2.getName()); // 输出:com.example.reflection.User

        // 方式 3:通过 Class.forName("全类名")(最常用,动态加载类)
        Class<?> clazz3 = Class.forName("com.example.reflection.User");
        System.out.println("方式 3:" + clazz3.getName()); // 输出:com.example.reflection.User

        // 验证 3 种方式获取的是同一个 Class 对象
        System.out.println(clazz1 == clazz2); // true
        System.out.println(clazz1 == clazz3); // true
    }
}

说明

  • 方式 1 需先创建对象,适用于已实例化的对象;
  • 方式 2 无需创建对象,适用于编译期已知的类;
  • 方式 3 通过类的全路径名动态加载,适用于编译期未知的类(如框架配置文件中指定的类)。

3.3 案例 2:通过反射获取类的信息

利用 Class 对象可以获取类的完整信息,包括类名、父类、接口、构造器、方法、属性等。

public class ReflectionDemo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("com.example.reflection.User");

        // 1. 获取类的基本信息
        System.out.println("类名(全路径):" + clazz.getName()); // com.example.reflection.User
        System.out.println("简单类名:" + clazz.getSimpleName()); // User
        System.out.println("父类:" + clazz.getSuperclass().getName()); // java.lang.Object
        System.out.println("实现的接口:");
        for (Class<?> iface : clazz.getInterfaces()) {
            System.out.println("  - " + iface.getName()); // java.io.Serializable
        }

        // 2. 获取构造器(getConstructors() 只能获取公有构造,getDeclaredConstructors() 可获取所有构造)
        System.out.println("\n所有构造器:");
        for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
            System.out.print("  构造器:");
            // 打印参数类型
            Class<?>[] paramTypes = constructor.getParameterTypes();
            for (Class<?> paramType : paramTypes) {
                System.out.print(paramType.getSimpleName() + " ");
            }
            System.out.println();
        }

        // 3. 获取成员方法(getMethods() 含父类公有方法,getDeclaredMethods() 只含本类所有方法)
        System.out.println("\n所有方法:");
        for (Method method : clazz.getDeclaredMethods()) {
            System.out.print("  方法:" + method.getName() + ",返回值:" + method.getReturnType().getSimpleName() + ",参数:");
            Class<?>[] paramTypes = method.getParameterTypes();
            for (Class<?> paramType : paramTypes) {
                System.out.print(paramType.getSimpleName() + " ");
            }
            System.out.println();
        }

        // 4. 获取成员变量(getFields() 只能获取公有属性,getDeclaredFields() 可获取所有属性)
        System.out.println("\n所有属性:");
        for (Field field : clazz.getDeclaredFields()) {
            System.out.println("  属性:" + field.getName() + ",类型:" + field.getType().getSimpleName());
        }
    }
}

输出结果片段

类名(全路径):com.example.reflection.User
简单类名:User
父类:java.lang.Object
实现的接口:
  - java.io.Serializable

所有构造器:
  构造器:
  构造器:String int
  构造器:String

所有方法:
  方法:getUsername,返回值:String,参数:
  方法:setUsername,返回值:void,参数:String
  方法:printInfo,返回值:void,参数:
  方法:toString,返回值:String,参数:

所有属性:
  属性:username,类型:String
  属性:age,类型:int
  属性:address,类型:String

3.4 案例 3:通过反射创建对象

反射可以动态创建类的实例,支持调用无参构造和有参构造。

public class ReflectionDemo3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> clazz = Class.forName("com.example.reflection.User");

        // 方式 1:调用无参构造(需确保类有无参构造,否则抛异常)
        User user1 = (User) clazz.newInstance(); // JDK 9+ 已过时,推荐用 getConstructor().newInstance()
        System.out.println("无参构造创建:" + user1); // User{username='null', age=0, address='null'}

        // 方式 2:调用有参构造(需指定参数类型)
        Constructor<?> constructor = clazz.getConstructor(String.class, int.class); // 获取公有有参构造
        User user2 = (User) constructor.newInstance("张三", 20); // 传入参数
        System.out.println("有参构造创建:" + user2); // User{username='张三', age=20, address='null'}

        // 方式 3:调用私有构造(需用 getDeclaredConstructor() 并设置可访问)
        Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class); // 获取私有构造
        privateConstructor.setAccessible(true); // 突破私有访问限制
        User user3 = (User) privateConstructor.newInstance("李四");
        System.out.println("私有构造创建:" + user3); // User{username='李四', age=0, address='null'}
    }
}

说明

  • clazz.newInstance() 仅支持无参构造,且要求构造器为公有;
  • 调用私有构造或有参构造时,需先通过 getDeclaredConstructor(参数类型...) 获取构造器,再调用 setAccessible(true) 取消访问检查(针对私有成员),最后用 newInstance(参数值...) 创建对象。

3.5 案例 4:通过反射调用方法

反射可以动态调用对象的方法(包括公有方法、私有方法、静态方法)

public class ReflectionDemo4 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> clazz = Class.forName("com.example.reflection.User");
        User user = (User) clazz.getConstructor(String.class, int.class).newInstance("王五", 25);

        // 1. 调用公有方法(getUsername())
        Method getUsernameMethod = clazz.getMethod("getUsername"); // 获取公有方法
        String username = (String) getUsernameMethod.invoke(user); // 调用方法,传入对象
        System.out.println("调用公有方法 getUsername():" + username); // 王五

        // 2. 调用私有方法(setUsername())
        Method setUsernameMethod = clazz.getDeclaredMethod("setUsername", String.class); // 获取私有方法
        setUsernameMethod.setAccessible(true); // 允许访问私有方法
        setUsernameMethod.invoke(user, "赵六"); // 传入对象和参数
        System.out.println("调用私有方法后:" + user.getUsername()); // 赵六

        // 3. 调用静态方法(printInfo())
        Method printInfoMethod = clazz.getMethod("printInfo"); // 获取静态方法
        printInfoMethod.invoke(null); // 静态方法无需传入对象,用 null 代替
    }
}

输出结果

调用公有方法 getUsername():王五
调用私有方法后:赵六
这是 User 类的静态方法

3.6 案例 5:通过反射操作属性

反射可以动态获取和修改对象的属性(包括私有属性)

public class ReflectionDemo5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Class<?> clazz = Class.forName("com.example.reflection.User");
        User user = (User) clazz.newInstance();

        // 1. 操作公有属性(address)
        Field addressField = clazz.getField("address"); // 获取公有属性
        addressField.set(user, "北京市"); // 设置属性值
        System.out.println("公有属性 address:" + addressField.get(user)); // 北京市

        // 2. 操作私有属性(username 和 age)
        // 操作 username
        Field usernameField = clazz.getDeclaredField("username"); // 获取私有属性
        usernameField.setAccessible(true); // 允许访问私有属性
        usernameField.set(user, "钱七"); // 设置值
        System.out.println("私有属性 username:" + usernameField.get(user)); // 钱七

        // 操作 age
        Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(user, 30); // 针对 int 类型的便捷方法
        System.out.println("私有属性 age:" + ageField.getInt(user)); // 30

        System.out.println("最终用户对象:" + user);
        // 输出:User{username='钱七', age=30, address='北京市'}
    }
}

四、反射的应用场景

  1. 动态代理:如 Spring AOP 基于反射实现方法增强;
  2. 框架开发:Spring IOC 通过反射创建 Bean,MyBatis 通过反射映射数据库字段与对象属性;
  3. 注解处理:如 @Override@Deprecated 的解析依赖反射;
  4. 序列化与反序列化:将对象转为字节流或从字节流恢复对象时使用。

五、反射的优缺点

优点

  • 灵活性高:可动态操作类和对象,适应多变的需求;
  • 可扩展性强:无需修改代码即可适配新类(如插件化开发)。

缺点

  • 性能开销:反射操作绕过编译期检查,运行时效率低于直接调用;
  • 破坏封装:可直接访问私有成员,违反面向对象的封装性;
  • 代码可读性差:反射代码较繁琐,调试难度高。

六、使用反射的注意事项

  1. 尽量避免在性能敏感场景使用反射(如高频调用的方法);
  2. 访问私有成员后,如需恢复访问权限,可再次调用 setAccessible(false)
  3. 反射操作可能抛出多种异常(如 ClassNotFoundExceptionNoSuchMethodException),需妥善处理;
  4. 避免过度依赖反射,优先使用直接调用(清晰且高效)。

七、总结

反射是 Java 中一种强大的动态编程机制,通过 Class 类和 java.lang.reflect 包的 API,我们可以在运行时获取类信息并动态操作对象。掌握反射不仅能理解框架的底层原理,还能在复杂场景下提升代码的灵活性。
但反射也存在性能和封装性问题,实际开发中需权衡利弊,合理使用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐