class.forname 会触发类初始化,执行静态代码块和静态变量赋值;classloader.loadclass 仅加载链接,不执行静态逻辑,直到首次主动使用才初始化。

Class.forName 会立即触发类初始化,静态代码块和静态变量赋值同步执行;ClassLoader.loadClass 只完成加载与链接,不执行任何静态逻辑,直到首次主动使用时才初始化。
静态代码块是否执行,取决于是否进入「初始化」阶段
Java 类加载分为加载(Load)、链接(Link)、初始化(Initialize)三个关键阶段。只有「初始化」阶段才会真正执行 static {} 块和给 static 变量赋值(非编译期常量)。两者的根本差异就卡在这里:
- Class.forName("com.example.Test") 默认等价于 Class.forName("com.example.Test", true, loader),强制走完初始化阶段 → 静态代码块立刻打印、static final 字段被真实赋值
- classLoader.loadClass("com.example.Test") 等价于 loadClass("com.example.Test", false),只到链接结束 → 静态块原封不动,字段仍为默认值(如 int 为 0,Object 为 null)
典型现象:JDBC 驱动注册失败就是静态块没跑
老版本 MySQL 驱动(如 mysql-connector-java 5.x)依赖 static 块自动注册:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) { /* ... */ }
}
- 用 Class.forName("com.mysql.jdbc.Driver") → 注册成功,后续 getConnection() 正常
- 误用 loadClass("com.mysql.jdbc.Driver") → 静态块跳过,DriverManager 不知道有这个驱动 → 报 No suitable driver found
类变量初始化时机的连锁影响
静态字段的值是否“到位”,直接影响反射调用、单元测试断言、SPI 加载等场景:
- static final String VERSION = getVersion(); —— 若 getVersion() 是耗时或依赖环境的方法,用 loadClass 就不会触发它
-
static final List
NAMES = Arrays.asList("a", "b"); —— 编译期常量,无论哪种方式都已在准备阶段赋默认值,但真正构造 ArrayList 实例仍需初始化阶段 - 子类初始化会强制父类初始化 → 即使你只 forName 子类,父类 static 块也会一并执行
如何安全切换或验证初始化状态
不建议凭感觉判断,可通过以下方式确认类是否已初始化:
- 加日志:在 static 块里输出明确标识,观察何时打印
- 检查字段值:用反射读取 static 字段,看是否为预期值而非默认值(如 null 或 0)
- 显式控制:若必须用 forName 但不想初始化,可写 Class.forName(name, false, loader),但语义不如 loadClass 清晰,且父类可能被连带初始化
- 避免陷阱:对 loadClass 返回的 Class 对象,不要直接调 .newInstance() 或 .getDeclaredFields(),先判空再确认是否已初始化











