问题描述
我知道Java会在首次访问中加载类(创建新实例,调用静态方法或静态字段),但是在这个简单的示例中,我尝试执行一个jar文件,该文件使用一些在运行时ClassPath中没有的类时间.我希望(由于在第一次访问时加载类)在发生异常之前以静态块和main方法打印我的消息.但我收到线程主"中的异常" java.lang.NoClassDefFoundError:com/example/DateAbstract",并且没有打印任何内容.当我在主类中使用抽象类或接口时,会发生这种情况,该类或接口位于另一个jar文件中.
I know Java loads Classes in first Access (creating new Instance, calling static method or static field), but in this simple example I try to execute a jar file that uses some classes which there aren't in my ClassPath at run time. I expect (because of loading classes in first access) print my messages in static block and main method before an exception occurred. but I got "Exception in thread "main" java.lang.NoClassDefFoundError: com/example/DateAbstract" and nothing printed.This occurred when I used an abstract class or interface in main class which that classes or interfaces are in another jar file.
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();
}
当我使用此代码在运行时将我的类添加到classpath时.没有改变.我在执行静态块之前得到执行.
when I use this code for add my classes to classpath at runtime. nothing changed. I got execption before execute static block.
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());
}
}
问题:为什么会发生这种情况以及如何解决呢?
Questions :why is this happening and how to solve it ?
推荐答案
在Java中,类是在其首次访问时加载的,这是不正确的.您会将其与类的初始化混淆,这意味着执行static
初始化程序块和字段初始化程序的Java代码.加载和验证可能会在更早的时间进行;在这方面,该规范为JVM提供了一些自由.
It’s not correct to say that in Java classes are loaded on their first access. You are confusing this with the initialization of a class, which implies executing the Java code of static
initializer blocks and field initializers. The loading and verification might happen at an earlier time; the specification provides some freedom to the JVMs in this regard.
此处的关键点是您的main
方法实例化类型为CustomDate
的对象,将其存储到编译时类型为DateAbstract
的变量中,然后尝试对该变量调用sayDate()
.实例化CustomDate
和对其调用DateAbstract.sayDate()
的这种组合需要验证其正确性,即CustomDate
是否为子类型DateAbstract
.因此,这两个类的加载将已经在验证时间中完成.
The key point here is that your main
method instantiates an object of type CustomDate
, stores it into a variable of the compile-time type DateAbstract
and then tries to invoke sayDate()
on that variable. This combination of instantiating CustomDate
and invoking DateAbstract.sayDate()
on it requires the verification of its correctness, i.e. whether CustomDate
is a subtype DateAbstract
. So the loading of these two classes will already happen at verification time.
您可以轻松地检查这是否是原因.如果将局部变量date
的类型更改为CustomDate
,则方法调用的实例化类型和接收器类型相同,因此可以在不加载类型的情况下证明其正确性,因此确实可以将其推迟到实例化CustomDate
的实际尝试,因此消息将被打印.
You can easily check that this is the cause. If you change the type of the local variable date
to CustomDate
, the instantiated type and the receiver type of the method invocation are the same, so the correctness can be proven without loading the type, so it will be indeed deferred to the actual attempt to instantiate CustomDate
, hence the messages will be printed.
不过,加载时间是特定于实现的细节.即使验证不是必需的,不同的JVM仍可以急切加载引用的类.确保延迟加载的唯一安全方法是使用动态加载,例如Class.forName(String)
.请注意,在以这种方式分离的类中,通常可以再次引用所有类型.因此,如果在调整类路径之后执行一次动态加载,则对您编写代码的方式及其性能没有太大影响.当然,在同一个类中让代码调整类路径和依赖于它的代码将无法可靠地工作.
Still, the loading time is an implementation-specific detail. A different JVM could load the referenced classes eagerly, even if they are not required for verification. The only safe way to ensure a deferred loading, is to use dynamic loading, e.g. Class.forName(String)
. Note that within the class detached this way, all types might be again referenced ordinarily. So if you do the dynamic loading once after the class path has been adjusted, there is not much impact on how you have to write the code nor its performance. Of course, having the code adjusting the class path and the code depending on it within the same class won’t work reliably.
这篇关于在主类中使用抽象或接口时,在运行时将类添加到类路径时出现NoClassDefFoundError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!