class.forname 是反射的起点,它加载类并执行初始化,获取可操作的 class 对象;而类名.class 和 obj.getclass() 不触发初始化。

Class.forName 是进入反射世界的第一道门——它不只是“找一个类”,而是把类从磁盘字节码拉进 JVM,完成加载、链接、初始化全过程,拿到 Class 对象后,所有元数据才真正可读可用。
Class.forName 的核心行为:加载 + 初始化
调用 Class.forName("com.example.User") 会做三件事:定位 class 文件、解析字节码生成 Class 实例、执行该类的静态代码块和静态字段初始化。这和 类名.class 或 obj.getClass() 不同——后两者不触发初始化,只返回已存在的 Class 对象。
- 如果类不存在或路径错误,抛出 ClassNotFoundException
- 如果静态块中抛异常,会包装成 ExceptionInInitializerError
- 默认使用当前线程上下文类加载器(ContextClassLoader),适合插件、模块化场景
拿到 Class 对象后,能读哪些元数据?
Class 对象是元数据入口,所有结构信息都可通过它提取:
-
getName():返回全限定名,如
com.example.User - getSuperclass():获取直接父类 Class 对象(Object 返回 null)
- getInterfaces():返回实现的所有接口 Class 数组
- getDeclaredFields():获取本类声明的所有字段(含 private),不含继承字段
- getDeclaredMethods():获取本类声明的所有方法(含 private、static),不含继承方法
- getConstructors():获取所有 public 构造器;getDeclaredConstructors() 获取全部构造器
常见用途与注意事项
动态加载不是“炫技”,而是为解耦留出空间:
- JDBC 驱动注册:Class.forName("com.mysql.cj.jdbc.Driver") 触发 Driver 的静态注册逻辑
- 配置驱动型框架:XML 或 properties 中写死类名,运行时按需加载,避免编译依赖
- 插件系统:主程序不引用插件类,靠字符串加载,插件升级无需重编译主程序
- 注意安全限制:某些环境(如 Applet、安全管理器启用时)会拦截反射操作,需显式 setAccessible(true) 才能访问私有成员
一个完整小例子
假设有一个 User 类在包 demo 下:
package demo;
public class User {
private String name;
public User(String name) { this.name = name; }
public void say() { System.out.println("Hi"); }
}
运行时加载并检查:
try {
Class> clazz = Class.forName("demo.User");
System.out.println("类名:" + clazz.getName()); // demo.User
System.out.println("父类:" + clazz.getSuperclass().getSimpleName()); // Object
System.out.println("构造器数:" + clazz.getDeclaredConstructors().length); // 1
System.out.println("方法数:" + clazz.getDeclaredMethods().length); // 至少 1(say)
} catch (ClassNotFoundException e) {
System.err.println("类未找到:" + e.getMessage());
}
不复杂但容易忽略:Class.forName 是反射的起点,它带初始化特性决定了你拿到的是“活”的类,而不是一个空壳。后续所有字段读写、方法调用、对象创建,都建立在这个基础之上。











