Java 当我在主类中使用抽象或接口时,在运行时将类添加到类路径时出现NoClassDefFoundError

Java 当我在主类中使用抽象或接口时,在运行时将类添加到类路径时出现NoClassDefFoundError,java,classpath,noclassdeffounderror,urlclassloader,dynamic-class-loaders,Java,Classpath,Noclassdeffounderror,Urlclassloader,Dynamic Class Loaders,我知道Java在第一次访问时加载类(创建新实例、调用静态方法或静态字段),但在这个简单的示例中,我尝试执行一个jar文件,该文件使用一些在运行时不在类路径中的类。我期望(因为在第一次访问中加载类)在异常发生之前在静态块和main方法中打印消息。但是我得到了“线程中的异常”main“java.lang.NoClassDefFoundError:com/example/DateAbstract”,并且没有打印任何内容。 当我在另一个jar文件中的主类中使用了一个抽象类或接口时,就会出现这种情况 pu

我知道Java在第一次访问时加载类(创建新实例、调用静态方法或静态字段),但在这个简单的示例中,我尝试执行一个jar文件,该文件使用一些在运行时不在类路径中的类。我期望(因为在第一次访问中加载类)在异常发生之前在静态块和main方法中打印消息。但是我得到了“线程中的异常”main“java.lang.NoClassDefFoundError:com/example/DateAbstract”,并且没有打印任何内容。 当我在另一个jar文件中的主类中使用了一个抽象类或接口时,就会出现这种情况

public class Driver {
static { System.out.println("I am first.[static block]"); }
public static void main(String[] args) {
    System.out.println("I am first.[ main method]");
    DateAbstract date = new CustomDate();
    System.out.println(date.sayDate());
}
在我的另一个罐子里:

public class CustomDate extends DateAbstract {
@Override
public String sayDate() {
    return new Date().toString();
}
public abstract class DateAbstract {
public abstract String sayDate();

}
当我在运行时使用此代码将我的类添加到类路径时。没有什么变化。我在执行静态块之前得到了execption

public class Driver {
static {
    System.out.println("I am first.[static block]");
    try {
        URL url = new File("lib/DateApi.jar").toURI().toURL();
        URLClassLoader urlClassLoader = (URLClassLoader) URLClassLoader.getSystemClassLoader();
        Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);
        method.invoke(urlClassLoader,url);
    } catch (Exception e) {
        e.printStackTrace();
    }

}
public static void main(String[] args) {
    System.out.println("I am first.[ main method]");
    DateAbstract date = new CustomDate();
    System.out.println(date.sayDate());
}

}
问题:
为什么会发生这种情况以及如何解决

说Java中的类在第一次访问时加载是不正确的。您将其与类的初始化混淆,这意味着执行
静态
初始值设定项块和字段初始值设定项的Java代码。加载和验证可能提前进行;该规范在这方面为JVM提供了一些自由

这里的关键点是,
main
方法实例化类型为
CustomDate
的对象,将其存储到编译时类型为
DateAbstract
的变量中,然后尝试对该变量调用
sayDate()
。实例化
CustomDate
并在其上调用
DateAbstract.sayDate()
需要验证其正确性,即
CustomDate
是否为子类型
DateAbstract
。因此,这两个类的加载将在验证时发生

你可以很容易地确定这是原因。如果将局部变量
date
的类型更改为
CustomDate
,则方法调用的实例化类型和接收方类型是相同的,因此可以在不加载类型的情况下证明其正确性,因此它实际上将推迟到实例化
CustomDate
的实际尝试,因此,信息将被打印出来

不过,加载时间是一个特定于实现的细节。不同的JVM可以急切地加载引用的类,即使它们不是验证所必需的。确保延迟加载的唯一安全方法是使用动态加载,例如
Class.forName(String)
。注意,在以这种方式分离的类中,所有类型都可能再次被引用。因此,如果在调整了类路径之后进行一次动态加载,则对编写代码的方式及其性能没有太大影响。当然,让调整类路径的代码和依赖它的代码在同一个类中运行是不可靠的