1. 简介
ClassNotFoundException 和 NoClassDefFoundError 都发生在 JVM 无法在类路径(classpath)中找到请求的类时。虽然看起来相似,但两者存在核心差异。
本文将探讨它们出现的原因及解决方案。
2. ClassNotFoundException
ClassNotFoundException 是一个受检异常(checked exception),当应用程序尝试通过类的完全限定名(fully-qualified name)加载类,但在类路径中找不到其定义时抛出。
这种情况主要发生在使用以下方法时:
Class.forName()
ClassLoader.loadClass()
ClassLoader.findSystemClass()
因此,在使用反射(reflection)时需特别注意 java.lang.ClassNotFoundException
。
举个典型例子:尝试加载 JDBC 驱动类但未添加必要依赖,就会抛出 ClassNotFoundException:
@Test(expected = ClassNotFoundException.class)
public void givenNoDrivers_whenLoadDriverClass_thenClassNotFoundException()
throws ClassNotFoundException {
Class.forName("oracle.jdbc.driver.OracleDriver");
}
3. NoClassDefFoundError
NoClassDefFoundError 是一个致命错误(fatal error)。当 JVM 尝试以下操作时无法找到类定义就会发生:
- 使用
new
关键字实例化类 - 通过方法调用加载类
这个错误的特点是:编译器能成功编译类,但 Java 运行时无法定位类文件。通常发生在类的静态块(static block)或静态字段(static field)初始化时抛出异常,导致类初始化失败。
看一个简单复现场景:ClassWithInitErrors
类初始化时抛出异常。首次创建对象会得到 ExceptionInInitializerError
,再次尝试加载同一类就会触发 NoClassDefFoundError
:
public class ClassWithInitErrors {
static int data = 1 / 0; // 静态初始化抛出异常
}
public class NoClassDefFoundErrorExample {
public ClassWithInitErrors getClassWithInitErrors() {
ClassWithInitErrors test;
try {
test = new ClassWithInitErrors(); // 首次初始化失败
} catch (Throwable t) {
System.out.println(t); // 捕获 ExceptionInInitializerError
}
test = new ClassWithInitErrors(); // 二次加载触发 NoClassDefFoundError
return test;
}
}
测试用例验证:
@Test(expected = NoClassDefFoundError.class)
public void givenInitErrorInClass_whenloadClass_thenNoClassDefFoundError() {
NoClassDefFoundErrorExample sample = new NoClassDefFoundErrorExample();
sample.getClassWithInitErrors();
}
4. 解决方案
诊断和修复这两个问题可能很耗时,但根本原因都是运行时类路径中缺少类文件。可采取以下措施:
✅ 检查类路径
确保类或包含该类的 jar 包在类路径中可用。若缺失需添加依赖。⚠️ 排查类路径覆盖
若类存在但问题依旧,可能是类路径被覆盖。需定位应用程序使用的确切类路径。🔍 类加载器问题
当应用使用多个类加载器时,一个类加载器加载的类可能对其他加载器不可见。深入理解 Java 类加载机制 是排查关键。
5. 总结
虽然两者都与类路径和运行时找不到类有关,但核心区别在于:
- ClassNotFoundException:运行时动态加载类(通过字符串类名)时找不到类
- NoClassDefFoundError:编译时类存在,但运行时因初始化失败等原因导致类不可用
完整示例代码见 GitHub 仓库。