1

我知道 Java 在第一次访问中加载类(创建新实例、调用静态方法或静态字段),但在这个简单的示例中,我尝试执行一个 jar 文件,该文件使用一些在运行时我的 ClassPath 中没有的类。我希望(因为在第一次访问中加载类)在发生异常之前在静态块和 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();

}

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

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());
}

}

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

4

1 回答 1

1

说 Java 类在第一次访问时加载是不正确的。您将此与类的初始化混淆了,这意味着执行static初始化程序块和字段初始化程序的 Java 代码。加载和验证可能发生在较早的时间;该规范在这方面为 JVM 提供了一些自由度。

这里的关键点是您的main方法实例化了一个 type 的对象CustomDate,将其存储到编译时类型的变量中DateAbstract,然后尝试调用sayDate()该变量。这种对它进行实例化CustomDate和调用DateAbstract.sayDate()的组合需要验证它的正确性,即是否CustomDate是一个子类型DateAbstract。所以这两个类的加载已经在验证时发生了。

您可以轻松检查这是否是原因。如果把局部变量的类型改成dateCustomDate实例化的类型和方法调用的接收者类型是一样的,所以不用加载类型就可以证明正确性,所以确实会推迟到实际尝试实例化CustomDate,因此将打印消息。

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

于 2016-08-31T18:22:35.050 回答