Java学习笔记之反射

前言

写 Java 代码时,你习惯了 new 一个对象、直接调方法------这些都在编译期 就确定了。但有时候,你需要在运行时才知道要操作哪个类:比如框架要读取配置文件来决定创建什么对象、IDE 的自动补全要列出类的方法、Spring 要扫描包并自动注入依赖。

这些需求都指向同一个机制------反射(Reflection)。它能让你在运行时动态地获取类的信息、创建对象、调用方法、访问字段------就像给程序装了一面"镜子",让它能看清自己。


一、概念:什么是反射

1.1 从一个问题开始

正常情况下,你写 Java 代码是这样的:

java 复制代码
// 编译期就确定了:创建一个 User 对象,调用 getName 方法
User user = new User();
String name = user.getName();

但如果类名和方法名来自配置文件呢?

properties 复制代码
# config.properties
class.name=net.feixiang.reflection.User
method.name=getName

编译时你根本不知道 class.name 是什么------只有运行时读了配置文件才知道。这时候就需要反射:

java 复制代码
// 从配置文件读取类名和方法名
String className = props.getProperty("class.name");    // "net.feixiang.reflection.User"
String methodName = props.getProperty("method.name");  // "getName"

// 运行时动态加载类、创建对象、调用方法------这就是反射
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(methodName);
String result = (String) method.invoke(obj);

1.2 反射的定义

一句话理解:反射是 Java 在运行时动态获取类信息、操作类成员(构造方法、字段、方法)的机制------让程序能"自省"和"自操作"。

Java 是静态语言,所有类型的确定都发生在编译期。反射打破了这一限制,让 Java 拥有了部分动态语言的能力。每一个被 JVM 加载的类,在内存中都对应一个 Class 对象------反射就是通过这些 Class 对象来获取和操作类的内部结构。


二、性质:Class 类与获取类信息

2.1 获取 Class 对象的四种方式

java 复制代码
// 方式一:类名.class ------ 编译期就知道类
Class<User> clazz1 = User.class;

// 方式二:对象.getClass() ------ 已经有对象了
User user = new User();
Class<? extends User> clazz2 = user.getClass();  // 返回实际运行时类型

// 方式三:Class.forName("全限定类名") ------ 类名来自字符串(最常用)
Class<?> clazz3 = Class.forName("net.feixiang.reflection.User");
// 注意:这种方式必须提供完整的包名+类名,且类必须在 classpath 中

// 方式四:类加载器 ------ 更底层的方式
Class<?> clazz4 = ClassLoader.getSystemClassLoader().loadClass("net.feixiang.reflection.User");

2.2 从 Class 对象能获取什么

java 复制代码
package net.feixiang.reflection;

import java.io.Serializable;

@ReflectInfo("用户实体类")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    public int age;

    private static final String TYPE = "HUMAN";

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

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

    public static void printType() {
        System.out.println(TYPE);
    }
}
java 复制代码
// 获取 Class 对象
Class<User> userClass = User.class;

// 1. 基本信息
String className = userClass.getName();               // 完整类名: net.feixiang.reflection.User
String simpleName = userClass.getSimpleName();        // 简单类名: User
Package pkg = userClass.getPackage();                 // 包名: net.feixiang.reflection
int modifiers = userClass.getModifiers();             // 修饰符: 1 (public)
Class<?> superClass = userClass.getSuperclass();      // 父类: java.lang.Object
Class<?>[] interfaces = userClass.getInterfaces();    // 实现的接口 (1 个): Serializable

// 2. 构造方法
Constructor<?>[] constructors = userClass.getConstructors(); // Public 构造 (2 个): User() / User(String,int)
Constructor<User> constructor = userClass.getConstructor(String.class, int.class); // 指定双参构造: User(String,int)
Constructor<?>[] declaredConstructors = userClass.getDeclaredConstructors(); // 所有构造(含私有, 3 个)

// 3. 字段
Field[] fields = userClass.getFields();              // Public 字段(含继承, 1 个): int age
Field ageField = userClass.getField("age");          // 指定字段: age
Field[] declaredFields = userClass.getDeclaredFields(); // 所有字段(含私有, 4 个)

// 4. 方法
Method[] methods = userClass.getMethods();             // Public 方法(含继承, 11 个)
Method getNameMethod = userClass.getMethod("getName"); // 指定方法: getName
Method[] declaredMethods = userClass.getDeclaredMethods(); // 所有方法(含私有, 3 个)

// 5. 注解
Annotation[] annotations = userClass.getAnnotations(); // 类上的注解 (1 个): @ReflectInfo

2.3 getXxx vs getDeclaredXxx 的区别

方法 范围 包含私有? 包含继承?
getFields() public 字段
getDeclaredFields() 本类所有字段
getMethods() public 方法
getDeclaredMethods() 本类所有方法
getConstructors() public 构造 不适用
getDeclaredConstructors() 本类所有构造 不适用

记忆技巧 :带 Declared 的 = "声明在本类的" + 包含私有 + 不包含继承。


三、操作:反射的核心操作

3.1 动态创建对象

java 复制代码
Class<User> userClass = User.class;

// 方式一:Class.newInstance() ------ 仅限无参构造(Java 9 已废弃)
// User user = userClass.newInstance();

// 方式二:Constructor.newInstance() ------ 推荐方式
// 无参构造
Constructor<User> noArgConstructor = userClass.getDeclaredConstructor();
User user1 = noArgConstructor.newInstance();

// 有参构造
Constructor<User> argConstructor = userClass.getDeclaredConstructor(String.class, int.class);
User user2 = argConstructor.newInstance("张三", 25);

// 包括私有构造(需要 setAccessible)
Constructor<User> privateConstructor = userClass.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);  // 绕过访问控制
User user3 = privateConstructor.newInstance("秘密创建");

3.2 访问和修改字段

java 复制代码
User user = new User("张三", 25);
Class<? extends User> clazz = user.getClass();

// 访问 public 字段
Field ageField = clazz.getField("age");
int age = (int) ageField.get(user);       // 获取 user 对象的 age 字段值
System.out.println("年龄:" + age);        // 年龄:25

ageField.set(user, 30);                    // 修改 age 的值
System.out.println(user.age);              // 30

// 访问 private 字段 ------ 需要 setAccessible(true)
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);             // 突破 private 限制
String name = (String) nameField.get(user);
System.out.println("姓名:" + name);       // 姓名:张三

nameField.set(user, "李四");
System.out.println(nameField.get(user));   // 李四

// 访问 static 字段 ------ get/set 时对象参数传 null
Field typeField = clazz.getDeclaredField("TYPE");
typeField.setAccessible(true);
String type = (String) typeField.get(null); // static 字段,对象传 null
System.out.println("类型:" + type);         // 类型:HUMAN

3.3 调用方法

java 复制代码
User user = new User("张三", 25);
Class<? extends User> clazz = user.getClass();

// 调用 public 方法
Method getNameMethod = clazz.getMethod("getName");
String name = (String) getNameMethod.invoke(user);
System.out.println("姓名:" + name);  // 姓名:张三

// 调用 private 方法 ------ 需要 setAccessible(true)
Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
setNameMethod.setAccessible(true);      // 突破 private 限制
setNameMethod.invoke(user, "李四");     // 调用 user.setName("李四")
System.out.println(getNameMethod.invoke(user));  // 李四

// 调用 static 方法 ------ invoke 时对象参数传 null
Method printTypeMethod = clazz.getDeclaredMethod("printType");
printTypeMethod.setAccessible(true);
printTypeMethod.invoke(null);           // 输出:HUMAN
// static 方法不依赖具体对象,所以第一个参数传 null

3.4 绕过泛型检查

反射可以突破编译期的泛型限制------这是它既强大又危险的一面:

java 复制代码
// 正常情况:泛型限制只能放 String
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123);  // 编译错误!Integer 不能放入 List<String>

// 用反射绕过泛型检查
Class<? extends List> listClass = list.getClass();
Method addMethod = listClass.getMethod("add", Object.class);
addMethod.invoke(list, 123);       // 成功!整型被塞进了字符串列表
addMethod.invoke(list, true);      // 布尔型也塞进去了

System.out.println(list);  // [hello, 123, true]

// 但取出来要当心了:
// String s = list.get(1);  // ClassCastException!里面是 Integer

四、场景:反射的典型应用

4.1 框架的核心------依赖注入

java 复制代码
// 模拟 Spring 的依赖注入:扫描注解,自动创建对象并注入
public class SimpleContainer {

    private Map<Class<?>, Object> beans = new HashMap<>();

    // 注册并自动注入
    public void register(Class<?> clazz) throws Exception {
        // 1. 创建对象
        Object instance = clazz.getDeclaredConstructor().newInstance();

        // 2. 扫描所有字段,发现 @Autowired 注解就注入
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                Class<?> fieldType = field.getType();
                Object dependency = beans.get(fieldType);  // 从容器获取依赖
                field.set(instance, dependency);            // 注入
            }
        }

        // 3. 放入容器
        beans.put(clazz, instance);
    }
}

4.2 JDBC 驱动加载

java 复制代码
// JDBC 连接数据库------通过反射加载驱动类
// 你只需要配置驱动类名,框架帮你加载
String driverClass = "com.mysql.cj.jdbc.Driver";
Class.forName(driverClass);  // 触发驱动的 static 代码块,自动注册到 DriverManager

Connection conn = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/mydb", "root", "password");

4.3 序列化与反序列化

java 复制代码
// JSON 序列化:把对象转为 JSON 字符串
public class SimpleJsonSerializer {

    // 序列化:遍历对象的所有字段,拼成 JSON
    public static String toJson(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        StringBuilder json = new StringBuilder("{");

        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);

            json.append("\"").append(field.getName()).append("\":");
            Object value = field.get(obj);
            if (value instanceof String) {
                json.append("\"").append(value).append("\"");
            } else {
                json.append(value);
            }

            if (i < fields.length - 1) json.append(", ");
        }
        json.append("}");
        return json.toString();
    }

    // 反序列化:从 JSON 字符串恢复对象
    public static <T> T fromJson(String json, Class<T> clazz) throws Exception {
        T obj = clazz.getDeclaredConstructor().newInstance();

        // 解析 JSON 字符串的简单实现(仅演示反射的用途)
        // 实际项目请用 Jackson、Gson 等成熟框架
        json = json.replace("{", "").replace("}", "").replace("\"", "");
        String[] pairs = json.split(",");

        for (String pair : pairs) {
            String[] kv = pair.split(":");
            String key = kv[0].trim();
            String value = kv[1].trim();

            Field field = clazz.getDeclaredField(key);
            field.setAccessible(true);

            // 根据字段类型转换值
            if (field.getType() == int.class || field.getType() == Integer.class) {
                field.set(obj, Integer.parseInt(value));
            } else if (field.getType() == String.class) {
                field.set(obj, value);
            }
            // 其他类型...
        }
        return obj;
    }
}

4.4 动态代理

java 复制代码
// 接口:用户服务
public interface UserService {
    void save(String name);
    String findById(int id);
}
java 复制代码
// 真实实现:用户服务
public class UserServiceImpl implements UserService {
    @Override
    public void save(String name) {
        System.out.println("保存用户:" + name);
    }

    @Override
    public String findById(int id) {
        return "用户" + id;
    }
}
java 复制代码
// 动态代理:在所有方法前后打印日志
public class LoggingHandler implements InvocationHandler {

    private Object target;  // 被代理的真实对象

    public LoggingHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>> 开始调用 " + method.getName());
        long start = System.currentTimeMillis();

        Object result = method.invoke(target, args);  // 反射调用真实方法

        long elapsed = System.currentTimeMillis() - start;
        System.out.println("<<< " + method.getName() + " 完成,耗时 " + elapsed + "ms");
        return result;
    }

    // 创建代理对象
    public static <T> T createProxy(T target, Class<T> interfaceClass) {
        return (T) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(),
                new Class<?>[]{interfaceClass},
                new LoggingHandler(target)
        );
    }
}

使用:

java 复制代码
// 获取代理对象,使用方式不变,但所有方法调用都会自动打印日志
UserService proxy = LoggingHandler.createProxy(new UserServiceImpl(), UserService.class);

proxy.save("张三");
// >>> 开始调用 save
// 保存用户:张三
// <<< save 完成,耗时 0ms

String result = proxy.findById(1001);
// >>> 开始调用 findById
// <<< findById 完成,耗时 0ms
System.out.println(result);  // 用户1001

五、举例:完整的代码示例

5.1 通用 Bean 拷贝工具

java 复制代码
// 利用反射实现 Bean 属性拷贝(简易版 BeanUtils)
public class BeanCopyUtils {

    /**
     * 将源对象的同名、同类型字段拷贝到目标对象
     */
    public static void copy(Object source, Object target) throws Exception {
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();

        // 遍历目标对象的所有字段
        for (Field targetField : targetClass.getDeclaredFields()) {
            // 过滤 static 和 final
            int mod = targetField.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }

            String fieldName = targetField.getName();

            try {
                // 在源对象中找同名字段
                Field sourceField = sourceClass.getDeclaredField(fieldName);

                // 类型必须匹配
                if (!targetField.getType().equals(sourceField.getType())) {
                    continue;
                }

                // 获取源字段值 -> 设置到目标字段
                sourceField.setAccessible(true);
                targetField.setAccessible(true);
                Object value = sourceField.get(source);
                targetField.set(target, value);

            } catch (NoSuchFieldException e) {
                // 源对象没有这个字段,跳过
            }
        }
    }
}
java 复制代码
// 源对象:从数据库查出来的 DTO
public class UserDTO {
    private String name;
    private int age;
    private String email;

    // 构造方法、getter/setter ...
    public UserDTO(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
}
java 复制代码
// 目标对象:返回给前端的 VO(多了 description 字段,少了 email)
public class UserVO {
    private String name;
    private int age;
    private String description;

    @Override
    public String toString() {
        return "UserVO{name='" + name + "', age=" + age + ", description='" + description + "'}";
    }
}

使用:

java 复制代码
UserDTO dto = new UserDTO("张三", 25, "zhangsan@feixiang.net");
UserVO vo = new UserVO();

BeanCopyUtils.copy(dto, vo);
System.out.println(vo);
// UserVO{name='张三', age=25, description='null'}
// name 和 age 被成功拷贝,description 和 email 因不匹配被跳过

5.2 注解驱动的测试框架

java 复制代码
// 自定义注解:标记测试方法
@Retention(RetentionPolicy.RUNTIME)
@interface TestCase {
    String description() default "";
}
java 复制代码
// 被测试类:计算器
public class Calculator {
    public int add(int a, int b) { return a + b; }
    public int divide(int a, int b) { return a / b; }
}
java 复制代码
// 测试运行器:扫描 @TestCase 方法,自动执行
public class TestRunner {

    public static void run(Class<?> testClass) throws Exception {
        Object instance = testClass.getDeclaredConstructor().newInstance();
        int passed = 0;
        int failed = 0;

        for (Method method : testClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(TestCase.class)) {
                TestCase annotation = method.getAnnotation(TestCase.class);
                System.out.print("测试: " + method.getName()
                        + (annotation.description().isEmpty() ? "" : " - " + annotation.description())
                        + " ... ");

                try {
                    method.invoke(instance);
                    System.out.println("通过");
                    passed++;
                } catch (InvocationTargetException e) {
                    Throwable cause = e.getCause();
                    System.out.println("失败:" + (cause != null ? cause.getMessage() : e.getMessage()));
                    failed++;
                } catch (Exception e) {
                    System.out.println("失败:" + e.getMessage());
                    failed++;
                }
            }
        }

        System.out.println("\n==========");
        System.out.println("通过: " + passed + ", 失败: " + failed);
    }
}

使用:

java 复制代码
// 编写测试类
public class CalculatorTest {

    private Calculator calculator = new Calculator();

    @TestCase(description = "加法测试")
    public void testAdd() {
        int result = calculator.add(2, 3);
        if (result != 5) throw new AssertionError("期望 5,实际 " + result);
    }

    @TestCase(description = "除法测试")
    public void testDivide() {
        int result = calculator.divide(10, 2);
        if (result != 5) throw new AssertionError("期望 5,实际 " + result);
    }

    @TestCase(description = "除零测试-应抛出异常")
    public void testDivideByZero() {
        calculator.divide(10, 0);  // 期望抛出 ArithmeticException
    }
}
java 复制代码
// 运行
TestRunner.run(CalculatorTest.class);
// 测试: testAdd - 加法测试 ... 通过
// 测试: testDivide - 除法测试 ... 通过
// 测试: testDivideByZero - 除零测试-应抛出异常 ... 失败:/ by zero
//
// ==========
// 通过: 2, 失败: 1

六、反例与注意事项

6.1 性能开销

反射比直接调用慢很多------每次反射调用都涉及类型检查、访问权限验证等额外步骤:

java 复制代码
// 直接调用
user.getName();  // 纳秒级(~1-10 ns)

// 反射调用
Method method = clazz.getMethod("getName");
method.invoke(user);  // 也是纳秒级,但慢几十到上百倍(~100-1000 ns)
// 注意:差距取决于 JVM 版本和优化程度,并非毫秒级

优化建议 :如果同一个反射方法要频繁调用,把 Method 对象缓存起来,调用时设置 setAccessible(true)

java 复制代码
private static final Map<String, Method> METHOD_CACHE = new HashMap<>();

public static Method getCachedMethod(Class<?> clazz, String name, Class<?>... paramTypes)
        throws NoSuchMethodException {
    String key = clazz.getName() + "#" + name;
    return METHOD_CACHE.computeIfAbsent(key, k -> {
        try {
            Method m = clazz.getDeclaredMethod(name, paramTypes);
            m.setAccessible(true);  // 关闭访问检查,减少性能开销
            return m;
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    });
}

6.2 破坏封装性

反射可以绕过 private 限制,破坏面向对象的封装性:

java 复制代码
// 反例:随意的 setAccessible(true) 绕过了类设计者的意图
class BankAccount {
    private double balance = 1000;

    // 唯一合法的取款方式------有校验
    public void withdraw(double amount) {
        if (amount > balance) throw new IllegalArgumentException("余额不足");
        balance -= amount;
    }
}
java 复制代码
// 用反射直接修改,跳过所有业务逻辑
BankAccount account = new BankAccount();
Field balanceField = BankAccount.class.getDeclaredField("balance");
balanceField.setAccessible(true);
balanceField.set(account, -999999);  // 余额被非法修改为负数!

反射是一把双刃剑------框架用它实现灵活性,但业务代码中滥用会破坏设计、引入安全风险。

6.3 类型安全丢失

反射操作绕过了编译期的类型检查,错误推迟到运行期:

java 复制代码
// 编译期不检查类型
Method method = clazz.getMethod("setAge", int.class);
method.invoke(user, "不是整数");  // 编译通过!运行时 IllegalArgumentException

6.4 反射的局限性

  1. 修改 final 字段行为未定义,强烈不建议尝试 :对于 static final 的基本类型和 String 常量,由于编译期内联,修改通常无效。
  2. 不能获取局部变量信息:方法内部的局部变量在反射中不可见。
  3. 不能获取泛型的实际类型参数(因为类型擦除)List<String>List<Integer> 在运行时都是 List,通过反射看不到泛型参数。

七、速查清单

问题 答案
什么是反射? 运行时动态获取类信息、操作类成员的机制
获取 Class 对象有哪些方式? 类名.class对象.getClass()Class.forName()、类加载器
getFields()getDeclaredFields() 的区别? 前者只拿 public + 包含继承;后者拿本类全部 + 包含私有
怎么访问私有成员? setAccessible(true) 后获取/调用
反射怎么创建对象? Constructor.newInstance()
反射怎么调用方法? Method.invoke(对象, 参数...)
反射怎么访问字段? Field.get(对象) / Field.set(对象, 值)
反射的主要应用场景? 框架(Spring)、序列化(Jackson)、JDBC 驱动加载、动态代理、IDE
反射的缺点是什么? 性能开销大、破坏封装性、丢失编译期类型安全
如何优化反射性能? 缓存 Method/Field 对象、关闭访问检查 setAccessible(true)
Class.forNameClassLoader.loadClass 的区别? 前者会触发类的 static 初始化,后者不会
getModifiers() 返回什么? 一个 int,通过位运算判断(如 Modifier.isPublic(mod)

八、面试口述:什么是反射

反射是 Java 在运行时动态操作类的能力------通过 Class 对象,你可以在程序运行过程中获取类的构造方法、字段、方法、注解等所有信息,并且可以创建对象、读写字段、调用方法,甚至绕过 private 访问限制。

获取 Class 对象的方式有四种:类名.class对象.getClass()Class.forName("全限定类名") 和类加载器。其中 Class.forName 最常用,因为它接受字符串参数,支持从配置文件动态加载类。

反射的核心操作有:通过 Constructor.newInstance() 创建对象;通过 Method.invoke(obj, args) 调用方法;通过 Field.get(obj)Field.set(obj, value) 读写字段。要访问私有成员,需要先调用 setAccessible(true) 突破访问控制。

反射是框架的灵魂------Spring 的依赖注入、Jackson 的序列化、MyBatis 的结果映射、JDK 动态代理,底层都是反射。但反射也有缺点:第一,性能比直接调用慢数十倍,频繁调用需要缓存 Method/Field 对象;第二,它破坏了封装性,可以绕过 private 限制;第三,编译期的类型安全检查在反射中失效,错误推迟到运行期。所以反射的正确使用姿势是------框架底层用它,业务代码尽量不用。

相关推荐
河阿里1 小时前
Spring Boot:整合Quartz集群部署指南
java·spring boot·后端
小肥君2 小时前
gpu安装milvus问题解决
java·eureka·milvus
砍材农夫2 小时前
物联网实战:Spring Boot MQTT | 模拟器Paho客户端拆解高性能
java·javascript·spring boot·后端·物联网·struts
电商API_180079052472 小时前
免 TOP 入驻,第三方淘宝商品详情 API 快速接入与代码示例
java·大数据·开发语言·数据库·爬虫·数据分析
IT空门:门主2 小时前
Java AI 开发框架终极对比:Spring AI vs Spring AI Alibaba vs AgentScope-Java
java·人工智能·spring·spring ai·ai alibaba·agentscope-java
知南x2 小时前
【DPDK核心知识了解】(2) 内核旁路与硬件交互
学习
零陵上将军_xdr2 小时前
后端转全栈学习-Day4-JavaScript 基础-2
开发语言·javascript·学习
未若君雅裁2 小时前
多线程项目场景:CountDownLatch、Future、Semaphore
java
衫水2 小时前
关于 AI 工程化 Harness 的一些笔记(2026/6/5)
人工智能·笔记