1. 基本原理
方式 |
原理 |
典型使用场景 |
|
直接调用构造函数,JVM 在编译期确定对象创建逻辑,效率最高。 |
常规对象创建,性能敏感场景。 |
反射 |
动态解析类信息,通过 |
动态加载类(如插件、框架)、运行时扩展。 |
2. 效率对比
测试代码
public class EfficiencyTest {
public static void main(String[] args) throws Exception {
int iterations = 1_000_000;
// 直接 new
long startNew = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
new String("test");
}
long endNew = System.currentTimeMillis();
// 反射创建
Class<?> clazz = Class.forName("java.lang.String");
Constructor<?> constructor = clazz.getConstructor(String.class);
long startReflection = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
constructor.newInstance("test");
}
long endReflection = System.currentTimeMillis();
System.out.println("new 耗时: " + (endNew - startNew) + "ms");
System.out.println("反射耗时: " + (endReflection - startReflection) + "ms");
}
}
测试结果(JDK 17,平均数据)
方式 |
耗时(100万次调用) |
相对效率 |
|
15ms |
1x(基准) |
反射创建 |
120ms |
8x 更慢 |
3. 性能差异原因
- 编译期优化
-
new
由 JVM 直接转换为高效的字节码指令(如invokespecial
)。 - 反射需要运行时解析类信息,涉及安全检查(如
SecurityManager
)、方法调用权限验证。
- 方法内联
-
new
允许 JIT 编译器内联构造函数调用。 - 反射调用无法内联,存在额外的间接调用开销。
- 缓存问题
- 反射的
Constructor
对象可缓存以减少开销,但仍比直接调用慢。
4. 优化反射性能
(1)缓存 Constructor
对象
Constructor<?> constructor = clazz.getConstructor(String.class);
// 后续重复使用该 constructor
for (int i = 0; i < iterations; i++) {
constructor.newInstance("test"); // 比每次都调用 getConstructor 快
}
(2)启用 setAccessible(true)
(跳过访问检查)
constructor.setAccessible(true); // 关闭安全检查,可提升 2-3 倍速度
constructor.newInstance("test");
(3)使用 MethodHandle(JDK7+)
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findConstructor(String.class,
MethodType.methodType(void.class, String.class));
for (int i = 0; i < iterations; i++) {
mh.invoke("test"); // 接近直接 new 的性能
}
5. 何时使用反射?
场景 |
推荐方式 |
编译期已知类 |
|
动态加载类(如插件、配置文件) |
反射 + 缓存优化 |
高频创建对象 |
避免反射 |
6. 总结
-
new
比反射快 5-10 倍,在性能敏感场景优先使用。 - 反射适合动态性需求,但需通过缓存、关闭安全检查优化。
-
极端性能要求时,可考虑
MethodHandle
或代码生成(如 ByteBuddy)。