您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
一位不愿透露姓名的用户 发布于2019年11月25日 23:19

Java 中的 ClassNotFoundException 和 NoClassDefFoundError 有什么区别 ?

2986 次浏览 读完需要≈ 1 分钟 Java

我知道,在 Java 中,当一个类不存在时会报错,但是有些时候报的异常是 java.lang.ClassNotFoundException,有些时候报的错误却是 java.lang.NoClassDefFoundError

我有点疑惑,不太清楚java.lang.ClassNotFoundExceptionjava.lang.NoClassDefFoundError 这两个报错信息之间有什么区别呢?

谁能帮忙解释下吗?

1 个回答

Ready · 4年前

关于 Class 的加载问题,一般有三个比较常见的异常(或错误):java.lang.ClassNotFoundExceptionjava.lang.NoClassDefFoundError,以及 java.lang.ExceptionInInitializerError

那么它们是怎么来的?有什么区别呢?

我们先来看看这几个类的层次结构:

ClassNotFoundException 的层次体系
ClassNotFoundException 的层次体系

NoClassDefFoundError 和 ExceptionInInitializerError 的层次体系
NoClassDefFoundError 和 ExceptionInInitializerError 的层次体系

从上面的图中,我们可以看出:ClassNotFoundException 继承自 ReflectiveOperationException(意即反射操作异常),

NoClassDefFoundErrorExceptionInInitializerError 继承自 LinkageError(意即依赖链接错误)。

官方文档

接着,我们再来看看 Java 官方文档的权威说明(链接是英文的,我简单翻译成中文):

java.lang.ClassNotFoundException

当应用尝试通过类的名称来加载该类时,具体方式如下:

  • 通过Class.forName()方法来加载;
  • 通过ClassLoader.findSystemClass()方法来加载;
  • 通过ClassLoader.loadClass()方法来加载。

如果找不到指定名称的类定义,则抛出该异常。

从JDK 1.4开始,已对该异常进行了改进,以符合通用异常链机制。

如果在加载类时还引起了其他异常,也可以在构造ClassNotFoundException异常时,将其他的异常作为可选参数传入,这也就是我们熟知的"Caused by"异常。构造时传入的可选异常,你可以通过getException()Throwable.getCause()方法获取到,前者是后者的"历史遗留产物(legacy method)"。

java.lang.NoClassDefFoundError

当 Java 虚拟机或一个ClassLoader类的实例尝试加载一个类的定义(作为常规方法调用或new表达式的一部分)时,如果找不到该类的定义,则抛出该错误。

这个类在当前正在执行的类被编译的时候还存在,但是在真正运行的时候该类就不存在了,此时就会抛出该错误。

java.lang.ExceptionInInitializerError

表示在静态初始化程序中发生了意外的异常。 抛出ExceptionInInitializerError表示在执行静态代码初始化或静态变量初始化期间发生了异常。

小结

看完官方文档的描述,我们差不多就能够明白这几个类之前的区别了,下面简单小结一下:

  • java.lang.ClassNotFoundException 一般是在通过反射方式根据类的名称加载类时触发,如果指定的类不存在,则抛出该异常。
  • java.lang.NoClassDefFoundError 一般是当前代码的执行需要依赖于另外一个类,在编译当前代码的时候,这个类都还好好的,但是在运行时,这个类却找不到了。这种情况就会抛出该错误。
  • java.lang.ExceptionInInitializerError 一般是程序在初始化执行代码的过程中,执行到某些静态代码块或静态变量(比如计算静态变量的值)时发生了异常,则会抛出该错误。

解决

如何修复这些异常或错误呢?

一般情况下,当找不到一个类时,就要:

  • 检查该类的名称是否有误。如果是内部类,记得将类名前的"."改为"$"符号,例如package.subpkg.OuterClass.InnerClass应该为package.subpkg.OuterClass$InnerClass
  • 检查 classpath 中对应路径下是否存在该类的.class字节码文件,或者是否存在包含该类的jar文件,以及jar文件是否存在多版本冲突。
  • 检查当前应用的 ClassLoader 的类加载机制是否有被特殊定制处理过,核实在该加载机制下,是否能够正确加载到该类文件。

如果你不知道缺失的 Class 文件在哪个jar文件中,你可以通过执行如下 Java 代码来查找:

// 此处以 Apache Commons Lang 3 包中的 StringUtils 类文件举例
// 请自行根据实际情况,修改为对应的类
URL location = StringUtils.class.getProtectionDomain().getCodeSource().getLocation();
System.out.println(location);
// 输出类似于:"file:/D:/Maven/repos/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar"

撰写答案