0

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

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

} 

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

回答

0

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

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

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

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