Java 反射现代实践速查表(JDK 11+/17+)
适用人群:想快速上手/查阅反射写法的 Java 工程师。\ 运行环境:建议 JDK 11+,最佳 JDK 17/21。\ 关键词:Class
、Field
、Method
、Constructor
、MethodHandle
、VarHandle
、Proxy
、Type
。
目录
-
- 反射是什么
-
- 获取
Class
的三种方式
- 获取
-
- 现代 实例化对象(替代
Class.newInstance()
)
- 现代 实例化对象(替代
-
- 访问字段
Field
- 访问字段
-
- 调用方法
Method
- 调用方法
-
- 构造器
Constructor
- 构造器
-
- 注解与参数信息
-
- 泛型反射:
Type
家族
- 泛型反射:
-
- JDK 9+ 模块化与反射访问(
--add-opens
/opens
) - 动态代理
Proxy
- MethodHandle / VarHandle(高性能反射)
- 性能优化与缓存
- 安全要点与限制
- 常见异常与排错速览
- 实用片段(可直接复用)
- 最佳实践清单
- 延伸阅读
- JDK 9+ 模块化与反射访问(
1. 反射是什么
- 运行时检查 类型结构(类/字段/方法/构造器/注解),并可读写字段 、调用方法 、创建对象。
- 常用于:依赖注入、ORM、序列化/反序列化、AOP、测试工具、插件系统等。
2. 获取 Class
的三种方式
plain
Class<?> c1 = String.class; // 字面量
Class<?> c2 = "Hello".getClass(); // 实例 -> Class
Class<?> c3 = Class.forName("java.lang.String"); // 全限定名加载
小贴士:基本类型与数组也有 Class
:int.class
、String[].class
。
3. 现代 实例化对象(替代 Class.newInstance()
)
不要再用 clazz.newInstance()
(JDK 9+ 已弃用)。\ 推荐使用无参构造器的标准替代:
plain
MyType obj = MyType.class.getDeclaredConstructor().newInstance();
- 有参构造:
plain
Constructor<MyType> ctor = MyType.class.getDeclaredConstructor(String.class, int.class);
ctor.setAccessible(true); // 若非 public
MyType obj = ctor.newInstance("Ocean", 42);
说明:getDeclaredConstructor
能拿到非 public 构造;遇到访问限制需结合模块化策略(见 §9)。
4. 访问字段 Field
plain
Field f = MyType.class.getDeclaredField("name"); // 含私有
f.setAccessible(true); // 破私有(受模块限制)
f.set(obj, "Ocean");
Object val = f.get(obj);
- 列出所有声明字段(不含父类):
plain
for (Field fd : MyType.class.getDeclaredFields()) {
fd.setAccessible(true);
System.out.println(fd.getName() + " : " + fd.getType());
}
5. 调用方法 Method
plain
Method m = MyType.class.getDeclaredMethod("sayHello", String.class);
m.setAccessible(true);
Object ret = m.invoke(obj, "World");
- 列出 public 方法(含继承):
plain
for (Method pm : MyType.class.getMethods()) {
System.out.println(pm);
}
6. 构造器 Constructor
- 获取并调用:
plain
Constructor<MyType> ctor = MyType.class.getDeclaredConstructor();
ctor.setAccessible(true);
MyType instance = ctor.newInstance();
- 列出所有声明构造器:
plain
for (Constructor<?> c : MyType.class.getDeclaredConstructors()) {
System.out.println(c);
}
7. 注解与参数信息
plain
// 类、字段、方法上的注解
MyAnno anno = MyType.class.getAnnotation(MyAnno.class);
// 方法参数名(需编译参数 -parameters)
Method m = MyType.class.getDeclaredMethod("foo", String.class, int.class);
for (Parameter p : m.getParameters()) {
System.out.println(p.getName() + " : " + p.getType());
}
8. 泛型反射:Type
家族
plain
Field f = Repo.class.getDeclaredField("data"); // e.g. Map<String, List<User>>
Type t = f.getGenericType();
if (t instanceof ParameterizedType pt) {
Type raw = pt.getRawType(); // Map
Type[] args = pt.getActualTypeArguments(); // [String, List<User>]
}
- 常见
Type
子接口:Class<?>
:普通类型ParameterizedType
:带泛型参数的类型,如List<String>
TypeVariable
:类型变量,如T
WildcardType
:通配符,如? extends Number
GenericArrayType
:泛型数组,如T[]
9. JDK 9+ 模块化与反射访问(--add-opens
/opens
)
- 现象 :对非导出包 的私有成员做反射(哪怕
setAccessible(true)
),会抛IllegalAccessException
或非法反射访问警告/失败。 - 解决 :
- 运行时打开包(适合应用层调试/临时兼容)
plain
java --add-opens my.module/com.example.internal=ALL-UNNAMED -jar app.jar
markdown
2. **模块描述符中显式开放**(长期方案)
plain
// module-info.java
open module my.module {
// 或者有选择地 opens 某些包
// opens com.example.internal to other.module;
}
- 注意 :JDK 17+ 对 JDK 内部 API 强封装更严格;尽量依赖标准 API,不要反射 JDK 内部包。
10. 动态代理 Proxy
- 在运行期为接口生成代理对象,常用于 AOP、拦截器、RPC Stub。
plain
interface Hello { String hi(String name); }
InvocationHandler h = (proxy, method, args) -> {
if (method.getName().equals("hi")) {
return "[LOG] " + args[0];
}
return method.invoke(proxy, args);
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(),
new Class<?>[]{Hello.class},
h
);
System.out.println(hello.hi("Ocean")); // [LOG] Ocean
如果需要代理类(非接口),可使用 ByteBuddy、CGLIB、Javassist 等字节码库。
11. MethodHandle / VarHandle(高性能反射)
**MethodHandle**
:更接近直接调用的可链接句柄,类型安全,常比Method.invoke
更快。
plain
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(MyType.class, "sayHello",
MethodType.methodType(String.class, String.class));
String r = (String) mh.invoke(obj, "World");
**VarHandle**
(JDK 9+):字段/数组/字节缓冲区的"变量句柄",支持原子/内存栅栏等操作:
plain
VarHandle NAME;
static {
try {
NAME = MethodHandles.lookup()
.in(MyType.class)
.findVarHandle(MyType.class, "name", String.class);
} catch (Exception e) { throw new RuntimeException(e); }
}
NAME.set(obj, "Ocean");
String v = (String) NAME.get(obj);
一般规则:频繁调用/性能敏感 → 优先考虑 MethodHandle/VarHandle
;一次性工具/低频 → 传统反射 API 足够。
12. 性能优化与缓存
- 缓存反射元数据 :
Field/Method/Constructor
查找与setAccessible
代价不小,建议按类型做缓存。 - 避免热路径反射 :把反射调用移出循环,或转换为
MethodHandle
并缓存句柄。 - JIT 热身:基准测试前做预热,避免冷启动干扰。
13. 安全要点与限制
- 反射破坏封装,需谨慎对外暴露;对不可信输入(类名/成员名)务必校验白名单。
- 在受限环境(安全管理器已废弃但仍有沙箱/容器策略)可能禁止反射访问。
- 对JDK 内部包的访问在现代 JDK 中被强封装,不要依赖非标准内部 API。
14. 常见异常与排错速览
ClassNotFoundException
:类名或类路径错误。NoSuchMethodException
/NoSuchFieldException
:签名或名称不匹配(包含参数类型/顺序)。IllegalAccessException
:访问权限/模块未opens
。InvocationTargetException
:被调用方法抛出的异常被包装在此处。InaccessibleObjectException
(JDK 16+ 常见):模块强封装导致;用--add-opens
或opens
解决。
15. 实用片段(可直接复用)
15.1 安全获取无参构造实例(现代写法)
plain
public static <T> T newInstance(Class<T> type) {
try {
Constructor<T> c = type.getDeclaredConstructor();
c.setAccessible(true);
return c.newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Cannot instantiate: " + type.getName(), e);
}
}
15.2 读/写私有字段(带模块检查提示)
plain
public static Object readField(Object target, String name) {
try {
Field f = target.getClass().getDeclaredField(name);
f.setAccessible(true); // 若报 InaccessibleObjectException,参见 §9
return f.get(target);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
15.3 遍历声明方法(含注解)
plain
for (Method m : clazz.getDeclaredMethods()) {
m.setAccessible(true);
System.out.println(m.getName());
for (Annotation a : m.getAnnotations()) {
System.out.println(" @" + a.annotationType().getSimpleName());
}
}
15.4 解析字段的泛型参数
plain
public static List<Class<?>> genericArgsOfField(Field f) {
Type t = f.getGenericType();
if (t instanceof ParameterizedType pt) {
List<Class<?>> list = new ArrayList<>();
for (Type arg : pt.getActualTypeArguments()) {
if (arg instanceof Class<?> cls) list.add(cls);
// 可扩展处理 WildcardType/TypeVariable 等
}
return list;
}
return List.of();
}
15.5 缓存 MethodHandle
plain
final class Handles {
private static final Map<String, MethodHandle> CACHE = new ConcurrentHashMap<>();
static MethodHandle vh(Class<?> c, String name, Class<?> ret, Class<?>... params) {
return CACHE.computeIfAbsent(c.getName() + "#" + name, k -> {
try {
return MethodHandles.lookup().findVirtual(c, name, MethodType.methodType(ret, params));
} catch (Throwable e) { throw new RuntimeException(e); }
});
}
}
16. 最佳实践清单
- ✅ 实例化 :
getDeclaredConstructor().newInstance()
取代Class.newInstance()
。 - ✅ 模块化 :跨模块反射请使用
--add-opens
或在module-info.java
中opens
。 - ✅ 性能 :高频反射改用
MethodHandle/VarHandle
,并缓存句柄/成员。 - ✅ 健壮性 :明确捕获
ReflectiveOperationException
分支,封装为业务异常。 - ✅ 安全:对外暴露的"类名/成员名"必须白名单校验;不要反射 JDK 内部包。
- ✅ 可维护性 :优先接口与多态,反射只用于必要环节。
17. 延伸阅读
- Oracle 教程:The Reflection API (系统讲解与注意事项)\ docs.oracle.com/javase/tuto...
- Dev.java 教程(较新的官方教学站点)\ dev.java/learn/refle...
- Java 平台模块系统(JEP 261):openjdk.org/jeps/261
- 强封装 JDK 内部(JEP 403):openjdk.org/jeps/403