Während sie durch die Java Language Specification Suche zu beantworten diese Frage , habe ich gelernt , dass
Bevor eine Klasse initialisiert wird, muss ihre direkte Oberklasse initialisiert werden, aber von der Klasse implementierte Schnittstellen werden nicht initialisiert. In ähnlicher Weise werden die Superschnittstellen einer Schnittstelle nicht initialisiert, bevor die Schnittstelle initialisiert wird.
Aus eigener Neugier habe ich es versucht und wie erwartet wurde die Schnittstelle InterfaceType
nicht initialisiert.
public class Example {
public static void main(String[] args) throws Exception {
InterfaceType foo = new InterfaceTypeImpl();
foo.method();
}
}
class InterfaceTypeImpl implements InterfaceType {
@Override
public void method() {
System.out.println("implemented method");
}
}
class ClassInitializer {
static {
System.out.println("static initializer");
}
}
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public void method();
}
Dieses Programm druckt
implemented method
Wenn die Schnittstelle jedoch eine default
Methode deklariert, erfolgt eine Initialisierung. Betrachten Sie die InterfaceType
Schnittstelle als
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
dann würde das gleiche Programm oben gedruckt
static initializer
implemented method
Mit anderen Worten, das static
Feld der Schnittstelle wird initialisiert ( Schritt 9 in der detaillierten Initialisierungsprozedur ) und der static
Initialisierer des zu initialisierenden Typs wird ausgeführt. Dies bedeutet, dass die Schnittstelle initialisiert wurde.
Ich konnte im JLS nichts finden, was darauf hindeutet, dass dies geschehen sollte. Versteh mich nicht falsch, ich verstehe, dass dies passieren sollte, falls die implementierende Klasse keine Implementierung für die Methode bereitstellt, aber was ist, wenn dies der Fall ist? Fehlt diese Bedingung in der Java-Sprachspezifikation, habe ich etwas verpasst oder interpretiere ich es falsch?
quelle
interface
in Java keine konkrete Methode definiert werden sollte. Ich bin also überrascht, dass derInterfaceType
Code kompiliert wurde.default
Methoden .Antworten:
Dies ist ein sehr interessantes Thema!
Es scheint, dass JLS-Abschnitt 12.4.1 dies definitiv abdecken sollte. Das Verhalten von Oracle JDK und OpenJDK (javac und HotSpot) unterscheidet sich jedoch von den hier angegebenen. Das Beispiel 12.4.1-3 in diesem Abschnitt behandelt insbesondere die Schnittstelleninitialisierung. Das Beispiel wie folgt:
Die erwartete Leistung beträgt:
und tatsächlich bekomme ich die erwartete Ausgabe. Wenn der Schnittstelle jedoch eine Standardmethode hinzugefügt wird
I
,Die Ausgabe ändert sich zu:
Dies zeigt deutlich, dass die Schnittstelle
I
dort initialisiert wird, wo sie vorher nicht war! Das bloße Vorhandensein der Standardmethode reicht aus, um die Initialisierung auszulösen. Die Standardmethode muss nicht aufgerufen oder überschrieben oder gar erwähnt werden, und das Vorhandensein einer abstrakten Methode löst keine Initialisierung aus.Ich spekuliere, dass die HotSpot-Implementierung vermeiden wollte, dass die Überprüfung der Klassen- / Schnittstelleninitialisierung in den kritischen Pfad des
invokevirtual
Aufrufs aufgenommen wird. Vor Java 8 und Standardmethodeninvokevirtual
konnte niemals Code in einer Schnittstelle ausgeführt werden, sodass dies nicht auftrat. Man könnte meinen, dass dies Teil der Vorbereitungsphase für Klassen / Schnittstellen ( JLS 12.3.2 ) ist, die Dinge wie Methodentabellen initialisiert. Aber vielleicht ging dies zu weit und führte stattdessen versehentlich eine vollständige Initialisierung durch.Ich habe diese Frage auf der OpenJDK-Mailingliste für Compiler-Entwickler aufgeworfen . Es gab eine Antwort von Alex Buckley (Herausgeber des JLS), in der er weitere Fragen an die Implementierungsteams von JVM und Lambda stellt. Er merkt auch an, dass es hier einen Fehler in der Spezifikation gibt, der besagt, dass "T eine Klasse ist und eine von T deklarierte statische Methode aufgerufen wird", auch gelten sollte, wenn T eine Schnittstelle ist. Es kann also sein, dass es hier sowohl Spezifikations- als auch HotSpot-Fehler gibt.
Offenlegung : Ich arbeite für Oracle unter OpenJDK. Wenn die Leute denken, dass dies mir einen unfairen Vorteil verschafft, wenn ich die Prämie für diese Frage bekomme, bin ich bereit, flexibel zu sein.
quelle
Die Schnittstelle wird nicht initialisiert, da das konstante Feld
InterfaceType.init
, das durch einen nicht konstanten Wert (Methodenaufruf) initialisiert wird, nirgendwo verwendet wird.Es ist bekannt, dass zur Kompilierungszeit kein konstantes Feld der Schnittstelle verwendet wird und die Schnittstelle keine Standardmethode enthält (in Java-8), sodass die Schnittstelle nicht initialisiert oder geladen werden muss.
Die Schnittstelle wird in folgenden Fällen initialisiert:
Bei Standardmethoden implementieren Sie
InterfaceType
. Wenn alsoInterfaceType
Standardmethoden enthalten sind, wird es bei der Implementierung der Klasse VERERBT (verwendet) . Und die Initialisierung wird ins Bild kommen.Wenn Sie jedoch auf ein konstantes Feld der Schnittstelle zugreifen (das auf normale Weise initialisiert wird), ist die Schnittstelleninitialisierung nicht erforderlich.
Betrachten Sie den folgenden Code.
Im obigen Fall wird die Schnittstelle initialisiert und geladen, da Sie das Feld verwenden
InterfaceType.init
.Ich gebe nicht das Beispiel für die Standardmethode an, wie Sie dies bereits in Ihrer Frage angegeben haben.
Die Java-Sprachspezifikation und das Beispiel finden Sie in JLS 12.4.1 (Beispiel enthält keine Standardmethoden.)
Ich kann JLS für Standardmethoden nicht finden, es gibt möglicherweise zwei Möglichkeiten
quelle
default
Methode enthält und eine Klasse, die die Schnittstelle implementiert, initialisiert wird.Die Datei instanceKlass.cpp aus dem OpenJDK enthält die Initialisierungsmethode
InstanceKlass::initialize_impl
, die der detaillierten Initialisierungsprozedur im JLS entspricht, die sich analog im Abschnitt Initialisierung in der JVM-Spezifikation befindet.Es enthält einen neuen Schritt, der im JLS und nicht im JVM-Buch, auf das im Code verwiesen wird, nicht erwähnt wird:
Daher wurde diese Initialisierung explizit als neuer Schritt 7.5 implementiert . Dies weist darauf hin, dass diese Implementierung einer bestimmten Spezifikation entsprach, die schriftliche Spezifikation auf der Website jedoch offenbar nicht entsprechend aktualisiert wurde.
BEARBEITEN: Als Referenz das Commit (ab Oktober 2012!), Bei dem der entsprechende Schritt in die Implementierung aufgenommen wurde: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
EDIT2: Zufälligerweise habe ich dieses Dokument über Standardmethoden im Hotspot gefunden, das am Ende eine interessante Randnotiz enthält:
quelle
Ich werde versuchen zu behaupten, dass eine Schnittstelleninitialisierung keine Nebenkanal-Nebenwirkungen verursachen sollte, von denen die Subtypen abhängen. Ob dies ein Fehler ist oder nicht oder wie Java ihn behebt, sollte keine Rolle spielen die Anwendung, in der die Schnittstellen initialisiert werden.
Im Fall von a
class
ist es allgemein anerkannt, dass es Nebenwirkungen verursachen kann, von denen Unterklassen abhängen. BeispielsweiseJede Unterklasse von
Foo
würde erwarten, dass sie 1000 USD auf der Bank sehen, irgendwo im Unterklassencode. Daher wird die Oberklasse vor der Unterklasse initialisiert.Sollten wir das nicht auch für Superintefaces tun? Leider sollte die Reihenfolge der Superschnittstellen nicht signifikant sein, daher gibt es keine genau definierte Reihenfolge, in der sie initialisiert werden können.
Daher sollten wir diese Art von Nebenwirkungen bei Schnittstelleninitialisierungen besser nicht feststellen. Schließlich
interface
ist es nicht für diese Funktionen (statische Felder / Methoden) gedacht, auf die wir der Einfachheit halber aufbauen.Wenn wir diesem Prinzip folgen, ist es uns daher egal, in welcher Reihenfolge Schnittstellen initialisiert werden.
quelle