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)。請注意,在這種分離的類中,所有類型可能會被再次引用。所以如果你在調整了類路徑之後再進行一次動態加載,那麼對編寫代碼的方式和性能沒有太大的影響。當然,讓代碼根據它在同一個類中調整類路徑和代碼將無法可靠地工作。