
在 Java 反射机制中,java.lang.Class 对象是所有操作的入口。无论是想在运行时创建对象、调用方法,还是获取注解,我们都必须先拿到这个“元数据对象”。
但在实际开发中,获取 Class 对象的方式有多种,它们在触发时机、性能以及是否触发初始化上有着本质区别。本文将带你深度剖析这四种方式。
在进入正题前,必须理解 JVM 加载类的一个重要细节。一个类的生命周期包含以下阶段:
<clinit>() 方法的过程。
所谓的初始化,最直观的表现就是:静态变量的赋值动作和静态代码块(static { ... })的执行。
如果你在编译期就已经明确知道要操作哪个类,这是最推荐的方式。
Class clazz = TargetObject.class;
这是反射中最常用的方式,通常用于从配置文件中读取类名字符串。
Class clazz = Class.forName("cn.javaguide.TargetObject");
ClassNotFoundException。
当你已经拥有一个对象实例时,可以通过它反向获取类型信息。
TargetObject obj = new TargetObject(); Class clazz = obj.getClass();
new 出来了,该类肯定已经完成了初始化。
通过类加载器直接加载。
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
特性 | 类名.class | Class.forName() | instance.getClass() | ClassLoader |
|---|---|---|---|---|
前提条件 | 编译期已知类名 | 类的全路径字符串 | 已有对象实例 | 类加载器实例 |
是否初始化 | 否 | 是 (默认) | 是 (已完成) | 否 |
编译期检查 | 是 | 否 | 是 | 否 |
使用场景 | 确定类型、泛型处理 | 配置文件、JDBC 驱动 | 运行时类型判断 | 插件系统、动态加载 |
为了看清谁触发了静态代码块,我们可以写一个简单的 Demo:
class Demo {
static {
System.out.println(">>> Demo 类的静态代码块执行了!");
}
}
public class ReflectionTest {
public static void main(String[] args) throws Exception {
System.out.println("--- 场景1:使用 .class ---");
Class c1 = Demo.class;
System.out.println("已获取 Class 对象");
System.out.println("\n--- 场景2:使用 ClassLoader ---");
Class c2 = ClassLoader.getSystemClassLoader().loadClass("Demo");
System.out.println("已获取 Class 对象");
System.out.println("\n--- 场景3:使用 Class.forName() ---");
Class c3 = Class.forName("Demo");
System.out.println("已获取 Class 对象");
}
}控制台输出:
--- 场景1:使用 .class ---
已获取 Class 对象
--- 场景2:使用 ClassLoader ---
已获取 Class 对象
--- 场景3:使用 Class.forName() ---
>>> Demo 类的静态代码块执行了!
已获取 Class 对象结论: .class 和 ClassLoader 不会激活静态逻辑,而 forName 会。
在开发中,我们该如何选择?
.class:只要能拿得到类名,它最快、最安全,且不会引起不必要的初始化开销。
Class.forName():如果你在写框架(如 MyBatis 扫描实体类),或者根据配置加载驱动,这是不二之选。
ClassLoader:如果你希望类在真正被 newInstance() 之前保持“静默”,使用它。
作者:[予枫] 参考来源:JavaGuide (javaguide.cn)