1. 概述

ClassNotFoundExceptionNoClassDefFoundError异常都是JVM在classpath中无法找到所需要的类时候发生。

尽管这两个异常相似。但是还有一些本质上的区别的。

在本文中,我们就来讨论一下这两个异常以及如何处理这个异常。

2. ClassNotFoundException

ClassNotFoundException是一个可检测异常。当应用程序尝试通过其完全限定的类名去加载类并且在类路径上找不到其class文件时,将发生此异常。

这个异常主要是在尝试使用Class.forName()ClassLoader.loadClass()ClassLoader.findSystemClass()加载类时发生的。因此,在使用反射时,我们需要格外小心java.lang.ClassNotFoundException

比如,尝试在不添加必要依赖项的情况下加载JDBC驱动程序类,将会发生ClassNotFoundException

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
public void test_ClassNotFoundException() {
  assertThrows(java.lang.ClassNotFoundException.class, () -> {
    Class.forName("oracle.jdbc.driver.OracleDriver");
  });
}

3. NoClassDefFoundError

NoClassDefFoundError是致命错误。当JVM在尝试执行以下操作时找不到类的定义时,就会发生这个错误:

  • 使用new关键字实例化一个类
  • 使用方法调用加载类

编译器可以成功编译类但Java运行时无法找到类文件时,将发生错误。

通常在执行静态块或初始化类的静态字段时发生异常之后再尝试加载这个类时候发生。

说的比较绕,我们来看一个例子。

比如,我们有一个类ClassWithInitErrors。定义一个静态int类型的字段,并初始化为1/0。这样当我们初始化ClassWithInitErrors就会发生ExceptionInInitializerError异常,之后我们再次加载ClassWithInitErrors

就会发生NoClassDefFoundError

ClassWithInitErrors类:

public class ClassWithInitErrors {
    static int data = 1 / 0;
}

测试代码:

@Test
public void test_NoClassDefFoundError(){
  ClassWithInitErrors test;
  try {
    test = new ClassWithInitErrors();
  } catch (Throwable t) {
    System.out.println(t);
  }
  assertThrows(NoClassDefFoundError.class, () -> { new ClassWithInitErrors();});
}

4. 分析

有时候,分析和修复这两个问题可能会非常耗时。

这两个问题的主要原因是运行时类文件(在类路径中)不可用。

让我们看一下在处理这两种异常时可以考虑的几种方法:

  1. 确保在类路径中是否有可用的类或包含该类的jar。如果没有,添加它。
  2. 如果在应用程序的类路径上可用,则很可能是类路径被覆盖。要解决这个问题,需要找到应用程序使用的确切类路径。
  3. 如果应用程序使用多个类加载器,则由一个类加载器加载的类可能无法由其他类加载器使用。要对其进行故障排除,必须了解类加载器如何在Java中工作。类加载器工作原理可以参考这里

5.总结

尽管这两个异常都与类路径有关,并且Java运行时无法在运行时找到类,但需要注意它们之间的区别。

Java运行时仅在运行时尝试加载类时抛出ClassNotFoundException,并且在运行时提供了类名。

对于*NoClassDefFoundError,*该类在编译时存在,但是Java运行时在运行时无法在Java类路径中找到它。

与往常一样,可以在GitHub上获得代码示例。