乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

Field与Method详解

大翔在代码评审会上指着朱璐的代码:"朱璐,你这个 BeanUtils.copyProperties 底层就是通过反射的 Field 和 Method 实现的,你知道它的性能开销吗?"

朱璐一愣:"不就是复制属性吗,还能有什么坑?"

白歌接过话头:"我来解释。Field.setAccessible(true) 虽然能突破私有访问限制,但每次调用都要做安全检查,不缓存的话性能会差很多。"

小崔在旁边听得云里雾里:"setAccessible 是什么意思?为什么要突破私有?"

Frank 总监走过来说:"小崔问得好。这正是反射的核心问题——如何在运行时操作类的内部结构。大翔,你来给小崔讲透。"


一、定义表

类/概念描述关键方法
Class代表一个类的运行时信息getFields(), getDeclaredFields(), getMethods(), getDeclaredMethods()
Field代表类的成员变量(字段)get(Object), set(Object, value), setAccessible(true), getName(), getType()
Method代表类的方法invoke(Object, args...), setAccessible(true), getName(), getReturnType(), getParameterTypes()
AccessibleObjectField 和 Method 的父类setAccessible(boolean), isAccessible()
Modifier解析修饰符(public/private/static等)isPublic(int), isPrivate(int), isStatic(int), isFinal(int)

二、getFields() vs getDeclaredFields() 对比

方法返回范围是否包含私有是否包含继承
getFields()当前类 + 父类 + 接口中所有 public 字段否是
getDeclaredFields()当前类中所有访问级别的字段是否
getMethods()当前类 + 父类 + 接口中所有 public 方法(含继承的 Object 方法)否是
getDeclaredMethods()当前类中所有访问级别的方法(不含继承)是否

三、Mermaid 类图:反射核心类层次


四、Mermaid 时序图:Method.invoke() 执行流程


五、Field 核心操作详解

5.1 获取 Field 对象

Class<Employee> clazz = Employee.class;

// 获取所有 public 字段(含继承的)
Field[] publicFields = clazz.getFields();

// 获取当前类所有字段(含 private,不含继承)
Field[] allFields = clazz.getDeclaredFields();

// 获取指定名称的 public 字段
Field nameField = clazz.getField("name");  // 仅 public

// 获取指定名称的字段(任何访问级别)
Field salaryField = clazz.getDeclaredField("salary");  // 含 private

5.2 读取和设置字段值

Field salaryField = clazz.getDeclaredField("salary");
salaryField.setAccessible(true);  // 突破 private 限制

// 读取
double salary = (double) salaryField.get(employeeInstance);

// 设置
salaryField.set(employeeInstance, 18888.88);

5.3 setAccessible 与安全性

setAccessible(true) 会绕过 Java 访问控制检查。如果在没有配置 --add-opens 的情况下(JDK 9+)访问 JDK 内部类,会抛出 InaccessibleObjectException。在 JDK 8 中,只要没有 SecurityManager,通常都可以成功 setAccessible。


六、Method 核心操作详解

6.1 获取 Method 对象

// 获取无参方法
Method getName = clazz.getDeclaredMethod("getName");

// 获取有参方法
Method setName = clazz.getDeclaredMethod("setName", String.class);

// 获取所有 public 方法(含继承的 Object 方法)
Method[] publicMethods = clazz.getMethods();

6.2 invoke 调用方法

Method setName = clazz.getDeclaredMethod("setName", String.class);
setName.invoke(employeeInstance, "大翔");

Method getName = clazz.getDeclaredMethod("getName");
String name = (String) getName.invoke(employeeInstance);

6.3 invoke 方法签名详解

public Object invoke(Object obj, Object... args)
    throws IllegalAccessException,
           IllegalArgumentException,
           InvocationTargetException
  • 第一个参数 obj:目标对象实例。如果是静态方法,传 null。
  • 第二个参数 args:可变参数,方法实参列表。
  • 返回值:如果方法返回 void,invoke 返回 null;基本类型会自动装箱。
  • InvocationTargetException:如果被调用的方法内部抛出异常,会被包装为 InvocationTargetException。可通过 getCause() 获取原始异常。

七、完整代码示例

示例1:飞翔科技通用对象属性复制工具

import java.lang.reflect.Field;
import java.util.*;

/**
 * 飞翔科技通用对象属性复制工具 —— 基于 Field 反射
 * 将源对象中非 null 的属性值复制到目标对象
 */
class FeixiangBeanCopier {

    /**
     * 将 src 中非 null 的字段值复制到 dest 的对应字段
     * @param src  源对象
     * @param dest 目标对象
     * @throws Exception 反射异常
     */
    public static void copyNonNull(Object src, Object dest) throws Exception {
        Class<?> srcClass = src.getClass();
        Class<?> destClass = dest.getClass();

        // 获取源对象所有字段
        Field[] srcFields = srcClass.getDeclaredFields();

        for (Field srcField : srcFields) {
            srcField.setAccessible(true);
            Object value = srcField.get(src);

            // 只复制非 null 的值
            if (value == null) continue;

            try {
                // 在目标类中查找同名字段
                Field destField = destClass.getDeclaredField(srcField.getName());
                destField.setAccessible(true);

                // 类型相同才复制
                if (destField.getType().equals(srcField.getType())) {
                    destField.set(dest, value);
                }
            } catch (NoSuchFieldException e) {
                // 目标类没有这个字段,跳过
            }
        }
    }

    /**
     * 打印对象所有字段和值
     */
    public static void printFields(Object obj, String label) throws Exception {
        System.out.println("--- " + label + " ---");
        Class<?> clazz = obj.getClass();
        for (Field f : clazz.getDeclaredFields()) {
            f.setAccessible(true);
            System.out.printf("  %-12s (%s) = %s\n",
                    f.getName(), f.getType().getSimpleName(), f.get(obj));
        }
        System.out.println();
    }
}

// 源对象类
class EmployeeSource {
    private Integer id = 1001;
    private String name = "大翔";
    private Double salary = 18888.88;
    private String department = "研发部";
    private String phone = null;       // 这个为 null,不会被复制
    private String address = "北京朝阳区";
}

// 目标对象类(部分字段不同)
class EmployeeTarget {
    private Integer id = 0;
    private String name = "未知";
    private Double salary = 0.0;
    private String department = "默认部门";
    private String phone = "13800138000";   // 已有值,不会被覆盖
    private String email = "default@feixiang.com";  // 源对象没有,保持不变
}

public class BeanCopyDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("========== 飞翔科技属性复制演示 ==========\n");

        EmployeeSource src = new EmployeeSource();
        EmployeeTarget dest = new EmployeeTarget();

        System.out.println("【复制前】");
        FeixiangBeanCopier.printFields(src, "源对象");
        FeixiangBeanCopier.printFields(dest, "目标对象");

        // 执行复制
        FeixiangBeanCopier.copyNonNull(src, dest);

        System.out.println("【复制后】");
        FeixiangBeanCopier.printFields(src, "源对象");
        FeixiangBeanCopier.printFields(dest, "目标对象(已更新)");

        System.out.println("关键点说明:");
        System.out.println("  1. phone 源为 null → 目标保持原值 '13800138000'");
        System.out.println("  2. email 源不存在 → 目标保持原值");
        System.out.println("  3. address 目标没有该字段 → 跳过");
        System.out.println("  4. name/salary/department 被成功复制");
        System.out.println("\n========== End ==========");
    }
}

运行输出:

========== 飞翔科技属性复制演示 ==========

【复制前】
--- 源对象 ---
  id           (Integer) = 1001
  name         (String) = 大翔
  salary       (Double) = 18888.88
  department   (String) = 研发部
  phone        (String) = null
  address      (String) = 北京朝阳区

--- 目标对象 ---
  id           (Integer) = 0
  name         (String) = 未知
  salary       (Double) = 0.0
  department   (String) = 默认部门
  phone        (String) = 13800138000
  email        (String) = default@feixiang.com

【复制后】
--- 源对象 ---
  id           (Integer) = 1001
  name         (String) = 大翔
  salary       (Double) = 18888.88
  department   (String) = 研发部
  phone        (String) = null
  address      (String) = 北京朝阳区

--- 目标对象(已更新) ---
  id           (Integer) = 1001
  name         (String) = 大翔
  salary       (Double) = 18888.88
  department   (String) = 研发部
  phone        (String) = 13800138000
  email        (String) = default@feixiang.com

关键点说明:
  1. phone 源为 null → 目标保持原值 '13800138000'
  2. email 源不存在 → 目标保持原值
  3. address 目标没有该字段 → 跳过
  4. name/salary/department 被成功复制
========== End ==========

示例2:飞翔科技动态调用员工管理系统方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 飞翔科技员工管理系统 —— 通过 Method.invoke() 动态调用
 */
class EmployeeManager {

    private Map<Integer, String> employees = new LinkedHashMap<>();

    public void addEmployee(int id, String name) {
        employees.put(id, name);
        System.out.println("[新增] 员工 " + id + " - " + name);
    }

    public String findEmployee(int id) {
        String name = employees.get(id);
        if (name != null) {
            return "找到员工: " + id + " -> " + name;
        }
        return "未找到员工 ID: " + id;
    }

    public void deleteEmployee(int id) {
        String removed = employees.remove(id);
        if (removed != null) {
            System.out.println("[删除] 员工 " + id + " - " + removed);
        } else {
            System.out.println("[删除] 员工 " + id + " 不存在");
        }
    }

    public List<String> listAllEmployees() {
        List<String> result = new ArrayList<>();
        for (Map.Entry<Integer, String> entry : employees.entrySet()) {
            result.add(entry.getKey() + " -> " + entry.getValue());
        }
        return result;
    }

    public double calculateSalary(double base, double bonus) {
        return base + bonus;
    }

    public static void staticMethod(String msg) {
        System.out.println("[静态方法] " + msg);
    }
}

/**
 * 动态方法调度器 —— 通过 Method.invoke() 动态执行
 */
class DynamicDispatcher {

    /**
     * 根据方法名和参数动态调用
     */
    public static Object dispatch(Object target, String methodName, Object... args)
            throws Exception {
        Class<?> clazz = target.getClass();

        // 匹配参数类型
        Class<?>[] paramTypes = new Class<?>[args.length];
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (arg instanceof Integer) {
                paramTypes[i] = int.class;   // 注意:反射中 Integer → int.class
            } else if (arg instanceof Double) {
                paramTypes[i] = double.class;
            } else {
                paramTypes[i] = arg.getClass();
            }
        }

        // 获取 Method 对象
        Method method = clazz.getDeclaredMethod(methodName, paramTypes);

        // 如果方法是非 public 的,设置可访问
        if (!java.lang.reflect.Modifier.isPublic(method.getModifiers())) {
            method.setAccessible(true);
        }

        // 执行调用
        long start = System.nanoTime();
        Object result = method.invoke(target, args);
        long end = System.nanoTime();

        System.out.printf("  [反射调用耗时] %s.%s() → %.2f μs\n",
                clazz.getSimpleName(), methodName, (end - start) / 1000.0);

        return result;
    }
}

public class MethodInvokeDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("========== 飞翔科技动态方法调度系统 ==========\n");

        EmployeeManager manager = new EmployeeManager();
        DynamicDispatcher dispatcher = new DynamicDispatcher();

        // 1. 动态添加员工
        System.out.println("【动态操作 1:添加员工】");
        dispatcher.dispatch(manager, "addEmployee", 1001, "大翔");
        dispatcher.dispatch(manager, "addEmployee", 1002, "白歌");
        dispatcher.dispatch(manager, "addEmployee", 1003, "小崔");
        dispatcher.dispatch(manager, "addEmployee", 1004, "朱璐");
        System.out.println();

        // 2. 动态查询员工
        System.out.println("【动态操作 2:查询员工】");
        Object result1 = dispatcher.dispatch(manager, "findEmployee", 1002);
        System.out.println("  结果: " + result1);

        Object result2 = dispatcher.dispatch(manager, "findEmployee", 9999);
        System.out.println("  结果: " + result2);
        System.out.println();

        // 3. 动态删除员工
        System.out.println("【动态操作 3:删除员工】");
        dispatcher.dispatch(manager, "deleteEmployee", 1004);
        System.out.println();

        // 4. 动态列出所有员工
        System.out.println("【动态操作 4:列出所有员工】");
        Object result3 = dispatcher.dispatch(manager, "listAllEmployees");
        System.out.println("  结果: " + result3);
        System.out.println();

        // 5. 动态计算薪资(基本类型参数)
        System.out.println("【动态操作 5:计算薪资】");
        Object result4 = dispatcher.dispatch(manager, "calculateSalary", 15000.0, 3888.88);
        System.out.println("  薪资计算结果: " + result4);
        System.out.println();

        // 6. 测试找不到方法的情况
        System.out.println("【动态操作 6:调用不存在的方法】");
        try {
            dispatcher.dispatch(manager, "nonExistentMethod");
        } catch (NoSuchMethodException e) {
            System.out.println("  ✗ 方法不存在: " + e.getMessage());
        }

        System.out.println("\n========== End ==========");
    }
}

运行输出:

========== 飞翔科技动态方法调度系统 ==========

【动态操作 1:添加员工】
[新增] 员工 1001 - 大翔
  [反射调用耗时] EmployeeManager.addEmployee() → 125.30 μs
[新增] 员工 1002 - 白歌
  [反射调用耗时] EmployeeManager.addEmployee() → 15.20 μs
[新增] 员工 1003 - 小崔
  [反射调用耗时] EmployeeManager.addEmployee() → 14.80 μs
[新增] 员工 1004 - 朱璐
  [反射调用耗时] EmployeeManager.addEmployee() → 14.50 μs

【动态操作 2:查询员工】
  [反射调用耗时] EmployeeManager.findEmployee() → 19.00 μs
  结果: 找到员工: 1002 -> 白歌
  [反射调用耗时] EmployeeManager.findEmployee() → 12.00 μs
  结果: 未找到员工 ID: 9999

【动态操作 3:删除员工】
[删除] 员工 1004 - 朱璐
  [反射调用耗时] EmployeeManager.deleteEmployee() → 14.00 μs

【动态操作 4:列出所有员工】
  [反射调用耗时] EmployeeManager.listAllEmployees() → 12.50 μs
  结果: [1001 -> 大翔, 1002 -> 白歌, 1003 -> 小崔]

【动态操作 5:计算薪资】
  [反射调用耗时] EmployeeManager.calculateSalary() → 16.00 μs
  薪资计算结果: 18888.88

【动态操作 6:调用不存在的方法】
  ✗ 方法不存在: ...nonExistentMethod

========== End ==========

八、易错场景

易错1:混淆 getFields() 与 getDeclaredFields()

class Parent {
    public int parentPublic = 1;
    private int parentPrivate = 2;
}

class Child extends Parent {
    public int childPublic = 3;
    private int childPrivate = 4;
}

public class ErrorDemo1 {
    public static void main(String[] args) {
        Class<Child> clazz = Child.class;

        // getFields() → 只拿 public,但包含继承的
        System.out.println("getFields():");
        for (Field f : clazz.getFields()) {
            System.out.println("  " + f.getName());  // parentPublic, childPublic
        }

        // getDeclaredFields() → 拿所有,但不包含继承的
        System.out.println("getDeclaredFields():");
        for (Field f : clazz.getDeclaredFields()) {
            System.out.println("  " + f.getName());  // childPublic, childPrivate
            // ← 注意:没有 parentPublic 和 parentPrivate!
        }
    }
}

关键结论:getDeclaredFields() 只获取当前类声明的字段,不包含从父类继承的。如果要做 ORM 映射,可能需要递归遍历父类。

易错2:invoke 基本类型参数时的自动装箱问题

public class ErrorDemo2 {
    // 方法签名使用基本类型
    public void setAge(int age) {
        System.out.println("Age set to: " + age);
    }

    public static void main(String[] args) throws Exception {
        Method m = ErrorDemo2.class.getDeclaredMethod("setAge", int.class);

        // 错误:传 Integer 对象
        Integer age = 25;
        m.invoke(new ErrorDemo2(), age); // 这可以工作,因为可变参数会自动装箱

        // 真正容易出错的是获取 Method 时:
        // 错误示范:
        // Method m2 = ErrorDemo2.class.getDeclaredMethod("setAge", Integer.class);
        // ↑ NoSuchMethodException! 因为反射中 int.class ≠ Integer.class

        // 正确的参数类型匹配:
        // int → int.class
        // long → long.class
        // boolean → boolean.class
        // double → double.class
    }
}

易错3:忽略 InvocationTargetException 的原始异常

public class ErrorDemo3 {
    public void riskyMethod() {
        throw new RuntimeException("业务异常:余额不足");
    }

    public static void main(String[] args) {
        try {
            Method m = ErrorDemo3.class.getDeclaredMethod("riskyMethod");
            m.invoke(new ErrorDemo3());
        } catch (InvocationTargetException e) {
            // 错误做法:只打印 InvocationTargetException
            e.printStackTrace();
            // 正确做法:获取原始异常
            Throwable cause = e.getCause();  // ← RuntimeException: 业务异常:余额不足
            System.out.println("真正的异常: " + cause.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

易错4:setAccessible 性能陷阱

public class ErrorDemo4 {
    private int value = 0;

    public static void main(String[] args) throws Exception {
        Field field = ErrorDemo4.class.getDeclaredField("value");
        ErrorDemo4 obj = new ErrorDemo4();
        int iterations = 1_000_000;

        // 错误做法:每次迭代都 setAccessible(不要这样!)
        long start1 = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            field.setAccessible(true);  // ← 每次都调用!
            field.set(obj, i);
        }
        long time1 = System.nanoTime() - start1;

        // 正确做法:只 setAccessible 一次,缓存 Field 对象
        field.setAccessible(true);  // ← 只调用一次
        long start2 = System.nanoTime();
        for (int i = 0; i < iterations; i++) {
            field.set(obj, i);
        }
        long time2 = System.nanoTime() - start2;

        System.out.println("每次 setAccessible: " + time1 / 1_000_000 + " ms");
        System.out.println("缓存 Field 对象:    " + time2 / 1_000_000 + " ms");
        // 输出(参考):
        // 每次 setAccessible: ~3500 ms
        // 缓存 Field 对象:    ~15 ms
    }
}

九、面试考点

考点1:getFields() 和 getDeclaredFields() 的区别?

答:

  • getFields():返回当前类和所有父类、接口中的 public 字段。私有字段不返回。
  • getDeclaredFields():返回当前类中声明的所有访问级别的字段(private、protected、public、默认),但不包含从父类继承的字段。
  • 同名规则也适用于 getMethods() vs getDeclaredMethods()。
  • 要做 ORM 框架需要遍历字段时,通常用 getDeclaredFields() 并递归遍历父类 super.getDeclaredFields()。

考点2:Method.invoke() 的参数和返回值如何处理基本类型?

答:

  • 获取 Method 时:基本类型参数必须精确匹配 int.class, long.class, double.class 等,不能用包装类 Integer.class 代替。
  • 调用 invoke 时:传入的参数会自动装箱/拆箱,因为 invoke 接受的是 Object...。
  • 返回值:如果方法是 void,invoke 返回 null;如果是基本类型返回,会自动装箱(如 int → Integer)。
  • JDK 8 新增:Method.getParameterCount() 可以方便获取参数个数。

考点3:setAccessible(true) 的作用和潜在问题?

答:

  • 作用:绕过 Java 访问控制检查,允许反射访问 private 成员,提高性能(跳过安全检查)。
  • 性能提升:setAccessible 后 invoke 会快很多,因为跳过了每次调用的权限检查。
  • 安全限制:如果 JVM 配置了 SecurityManager,setAccessible 可能被拒绝。
  • JDK 模块化影响:JDK 9+ 引入模块系统后,访问 JDK 内部类需要添加 --add-opens 参数。
  • 最佳实践:setAccessible 一次,缓存 Field/Method 对象复用,避免重复调用。

考点4:为什么要手动设置 setAccessible(true) 而不始终设为 true?性能影响有多大?

答:

  • 安全原因:Java 语言的设计初衷之一是封装,私有成员不应该被随意访问。setAccessible 是一种有意的"越权"操作。
  • 性能对比:不设 setAccessible 时,每次 invoke/get/set 都要执行一遍完整的访问控制检查(检查 public/private/protected、检查调用者是否有权限等)。设为 true 后,这些检查被跳过,性能提升 10-50 倍(取决于具体操作)。
  • JDK 8 建议:对于会被频繁调用的反射操作,务必缓存 Field/Method 对象并调用 setAccessible(true)。

大翔总结道:"Field 和 Method 是反射体系的两大基石。Field 让你在运行时读写对象状态,Method 让你在运行时调用对象行为。"

白歌补充:"但能力越大,责任越大。setAccessible 破坏了封装,invoke 有性能开销,InvocationTargetException 需要解包——这些都是生产环境中真实踩过的坑。"

朱璐若有所思:"原来 BeanUtils.copyProperties 底层就是 getDeclaredFields + set + get...我现在明白它的性能瓶颈在哪了。"

小崔兴奋地喊道:"我知道了!Spring 的 @Autowired 注入就是用 Field.set() 实现的!"

大翔笑着点头:"没错。下一节课我们就来实战:写一个迷你 IoC 容器。"


附:JDK 8 反射相关新增 API 速查

新增API说明
Executable.getParameterCount()获取方法/构造器的参数个数
Method.isDefault()判断接口中的默认方法
AnnotatedElement.getAnnotationsByType(Class)支持 @Repeatable 注解读取
Executable.getParameters()获取 Parameter 数组(需编译时 -parameters 开启)
上一页
Constructor 与对象创建
下一页
反射应用详解