Java 反射教程 JDK 25 实战 - 实践
一 核心概念与适用场景
- 反射(Reflection) 是 JVM 在运行时获取类结构并操作对象的能力,核心入口是 java.lang.Class,配合 java.lang.reflect 包中的 Method、Field、Constructor 等类完成元数据读取与动态调用。典型能力包括:动态创建实例、访问/修改字段(含私有)、调用方法(含私有)、获取注解、动态处理数组与泛型签名等。
- 适用场景:框架与库的底层能力(如 Spring IOC/AOP、JUnit 测试发现、Jackson/Gson 序列化、IDE 调试与展示)、动态代理(JDK 动态代理基于反射)、插件化与热加载、序列化/反序列化与对象映射等。
- 代价与风险:反射会绕过部分编译期优化,通常比直接调用慢;通过 setAccessible(true) 可突破访问控制,可能破坏封装与安全策略;反射代码更难静态分析与维护。自 Java 9 模块系统 起,对反射访问非公开成员施加了更严格的限制,需要在模块描述符显式开放。
- JDK 25 提示:语言与平台特性保持稳定,反射 API 基本用法与 JDK 8+ 一致;但在模块化项目(module-info.java)中,跨模块访问非公开成员必须声明 opens/exports,否则会抛出 InaccessibleObjectException。
二 环境准备与模块化要点
- JDK 25 安装与运行:确保
java -version输出包含 25;编译与运行使用javac --release 25与java --release 25(或省略--release使用默认版本)。 - 模块化项目结构(推荐):
src/ ├── module-info.java └── com.example.reflection/ ├── model/Person.java └── demo/ReflectionDemo.java - module-info.java 示例(允许反射访问包内成员):
module com.example.reflection { requires java.base; // 默认依赖,显式书写更清晰 // 如需被其他模块反射访问本模块的非公开成员: // opens com.example.reflection.model to java.base; } - 未使用模块(classical classpath):不受模块边界限制,但应避免在生产中滥用反射访问私有成员。
- 跨模块反射要点:目标类所在模块需对调用者模块 opens 包;若仅访问公共 API,通常无需 opens,但涉及字段/方法/构造器的私有访问仍需 setAccessible(true) 且在模块环境下可能受限。
三 核心 API 与 JDK 25 实战代码
获取 Class 对象的三种方式:
类.class、对象实例.getClass()、Class.forName("全限定名")。构造实例:优先使用 Constructor.newInstance()(支持私有构造器);旧式
Class.newInstance()仅能调用无参公有构造器且已不推荐。字段操作:
getDeclaredField/Field[]获取字段,setAccessible(true)后用set/get读写(含私有)。方法调用:
getDeclaredMethod/Method[]获取方法,setAccessible(true)后用invoke调用(含私有)。数组操作:使用 java.lang.reflect.Array 动态创建与访问。
泛型信息:通过 getGenericType() 与 ParameterizedType 获取字段/方法的泛型签名(类型擦除后仍保留签名信息)。
示例代码(放在
src/com/example/reflection/demo/ReflectionDemo.java):package com.example.reflection.demo; import com.example.reflection.model.Person; import java.lang.reflect.*; import java.util.Arrays; public class ReflectionDemo { public static void main(String[] args) { try { // 1) 获取 Class 对象 Class clazz = Person.class; // 也可 Class.forName("com.example.reflection.model.Person") // 2) 构造实例(公有构造器) Constructor noArgsConstructor = clazz.getDeclaredConstructor(); noArgsConstructor.setAccessible(true); // 若构造器为 private 才需要 Person p1 = (Person) noArgsConstructor.newInstance(); // 3) 带参构造器 Constructor paramConstructor = clazz.getDeclaredConstructor(String.class, int.class); Person p2 = (Person) paramConstructor.newInstance("Alice", 25); System.out.println("p2 = " + p2); // 4) 访问与修改字段(含私有) Field nameField = clazz.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p1, "Bob"); System.out.println("p1.name = " + nameField.get(p1)); // 5) 调用方法(含私有) Method setNameMethod = clazz.getDeclaredMethod("setName", String.class); setNameMethod.setAccessible(true); setNameMethod.invoke(p1, "Charlie"); System.out.println("p1 after setName = " + p1); // 6) 私有方法调用 Method privateMethod = clazz.getDeclaredMethod("greet", String.class); privateMethod.setAccessible(true); String greeting = (String) privateMethod.invoke(p1, "Reflection"); System.out.println("greet result = " + greeting); // 7) 数组操作 Class intArrayClass = int[].class; Object arr = Array.newInstance(int.class, 3); Array.set(arr, 0, 100); Array.set(arr, 1, 200); Array.set(arr, 2, 300); System.out.println("array[1] = " + Array.get(arr, 1)); // 8) 泛型字段类型读取(示例:List) Field listField = clazz.getDeclaredField("tags"); Type genericType = listField.getGenericType(); if (genericType instanceof ParameterizedType pt) { Type[] args = pt.getActualTypeArguments(); System.out.println("tags 泛型参数: " + Arrays.toString(args)); // [class java.lang.String] } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchFieldException e) { e.printStackTrace(); } } } 对应的模型类(放在
src/com/example/reflection/model/Person.java):package com.example.reflection.model; import java.util.List; public class Person { private String name; private int age; private Listtags; public Person() {} public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", tags=" + tags + '}'; } private void setName(String name) { this.name = "[private-set]" + name; } private String greet(String prefix) { return prefix + ", I'm " + name; } } 运行方式(模块化):
# 编译 javac --release 25 -d out src/module-info.java src/com/example/reflection/demo/ReflectionDemo.java src/com/example/reflection/model/Person.java # 运行 java --release 25 -p out -m com.example.reflection/com.example.reflection.demo.ReflectionDemo预期输出(顺序可能因实现细节略有差异):
p2 = Person{name='Alice', age=25, tags=null} p1.name = Bob p1 after setName = Person{name='Charlie', age=25, tags=null} greet result = Reflection, I'm Charlie array[1] = 200 tags 泛型参数: [class java.lang.String]常见异常与处理要点:
- ClassNotFoundException / NoClassDefFoundError:类路径或模块路径配置问题。
- NoSuchMethod/Field/ConstructorException:签名不匹配(参数类型、参数个数、是否为静态等)。
- IllegalAccessException:访问受限;对非公开成员先
setAccessible(true),在模块环境下需opens。 - InvocationTargetException:被调用方法内部抛出的实际异常(通过
getCause()获取)。 - InaccessibleObjectException(JDK 9+):跨模块反射访问非公开成员未开放;在
module-info.java中添加opens 包 to 调用者模块。
四 性能优化与最佳实践
- 缓存反射元数据:对高频使用的 Method/Field/Constructor 做
static final缓存,避免重复查找;必要时配合ClassValue做按类缓存。 - 减少安全检查开销:对需要反复调用的非公开成员,调用一次
setAccessible(true)后复用;在模块环境下,优先考虑设计上的公共 API,减少对私有成员的反射依赖。 - 优先公共 API 与标准机制:能用接口/继承/注解处理器/
MethodHandle(JDK 7+)/LambdaMetafactory(JDK 8+)实现的逻辑,优先选择;它们通常比反射更高效、类型更安全。 - 防御性编程:对反射调用进行参数校验、空值与异常处理;对
invoke返回值进行类型检查与转换保护。 - 模块化合规:跨模块反射访问非公开成员时,在
module-info.java中显式opens;对外暴露最小必要包,避免全包开放。 - 安全策略:在受管环境(如启用安全管理器)下,限制反射权限,避免通过反射破坏单例、绕过访问控制或执行不可信代码。
五 常见问题与排查清单
- 找不到类或方法:核对全限定名、方法名与参数类型;注意 基本类型/包装类型 的差异(如
int.class与Integer.class不同)。 - 非法访问异常:字段/方法/构造器为非公开时,需
setAccessible(true);在 JDK 9+ 模块 环境下,为目标包添加opens到调用者模块。 - 模块未读/未导出:若目标类不在
java.base,需在module-info.java中requires目标模块,并对需要反射访问的包opens。 - 泛型擦除导致类型丢失:运行时通过 getGenericType() / ParameterizedType 获取字段/方法的泛型签名,避免直接使用
getClass()获取泛型类型。 - 性能瓶颈:热点路径避免反射;缓存 Method/Field,减少重复查找;必要时用 MethodHandle 或代码生成替代。
浙公网安备 33010602011771号