0

Ich weiß Java lädt Klassen in ersten Zugriff (Erstellen neuer Instanz, statische Methode oder statische Feld aufrufen), aber in diesem einfachen Beispiel versuche ich Führen Sie eine JAR-Datei aus, die einige Klassen verwendet, die zur Laufzeit nicht in meinem ClassPath vorhanden sind. Ich erwarte (wegen der Ladeklassen beim ersten Zugriff) meine Nachrichten in statischen Blöcken und Hauptmethoden auszudrucken, bevor eine Ausnahme aufgetreten ist. aber ich habe "Ausnahme im Thread" main "java.lang.NoClassDefFoundError: com/example/DateAbstract" und nichts gedruckt. Dies trat auf, wenn ich eine abstrakte Klasse oder Schnittstelle in Hauptklasse verwendete, die diese Klassen oder Schnittstellen in einer anderen JAR-Datei sind.NoClassDefFoundError beim Hinzufügen von Klassen zum Klassenpfad zur Laufzeit, wenn ich abstrakte oder Schnittstelle in Hauptklasse

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

in meinem anderen Glas:

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

} 

wenn ich diesen Code für meine Klassen Classpath zur Laufzeit hinzufügen. Nichts hat sich geändert. Ich habe eine Ausnahme, bevor ich den statischen Block ausführen kann.

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

} 

Fragen: Warum ist das passiert und wie es zu lösen?

Antwort

0

Es ist nicht richtig zu sagen, dass in Java Klassen auf ihren ersten Zugriff geladen werden. Sie verwechseln dies mit der Initialisierung einer Klasse, die die Ausführung des Java-Codes static Initialisierungsblöcke und Feldinitialisierer impliziert. Das Laden und Verifizieren könnte zu einem früheren Zeitpunkt geschehen; Die Spezifikation bietet den JVMs in dieser Hinsicht einige Freiheit.

Der entscheidende Punkt hier ist, dass Ihr main Methode ein Objekt vom Typ instanziiert CustomDate, speichert sie in eine Variable der Kompilierung-Typ DateAbstract und versucht dann sayDate() auf diese Variable aufzurufen. Diese Kombination von Instanziieren von CustomDate und Aufrufen von DateAbstract.sayDate() erfordert die Überprüfung ihrer Richtigkeit, d. H. Ob CustomDate ein Subtyp DateAbstract ist. Das Laden dieser beiden Klassen erfolgt also bereits bei Verifikationszeit.

Sie können leicht überprüfen, ob dies die Ursache ist. Wenn Sie den Typ der lokalen Variablen date zu CustomDate ändern, sind der instanziierte Typ und der Empfängertyp des Methodenaufrufs gleich, so dass die Korrektheit bewiesen werden kann, ohne den Typ zu laden, so dass es tatsächlich auf den eigentlichen Versuch verschoben wird instanziieren CustomDate, daher werden die Nachrichten gedruckt.

Dennoch ist die Ladezeit ein implementierungsspezifisches Detail. Eine andere JVM könnte die referenzierten Klassen eifrig laden, auch wenn sie nicht zur Verifikation benötigt werden. Der einzige sichere Weg, um ein verzögertes Laden sicherzustellen, besteht darin, eine dynamische Belastung zu verwenden, z. Class.forName(String). Beachten Sie, dass innerhalb der auf diese Weise gelösten Klasse alle Typen möglicherweise erneut referenziert werden. Wenn Sie also das dynamische Laden einmal durchführen, nachdem der Klassenpfad angepasst wurde, hat das keinen großen Einfluss darauf, wie Sie den Code oder seine Leistung schreiben müssen. Natürlich funktioniert der Code, der den Klassenpfad und den davon abhängigen Code innerhalb derselben Klasse anpasst, nicht zuverlässig.