Warum wird die Rückgabetyp-Kovarianz für versteckte statische Methoden erzwungen?

8

Dieser Code wird aufgrund des String Rückgabetyps von staticMethod in Child nicht kompiliert.

%Vor%

Ich weiß, dass JLS 8 in §8.4.8.3, "Anforderungen beim Überschreiben und Verbergen" sagt:

  

Wenn eine Methodendeklaration d1 mit dem Rückgabetyp R1 den Wert überschreibt oder ausblendet   Deklaration einer anderen Methode d2 mit Rückgabetyp R2, dann muss d1 sein   return-type-substituierbar (§8.4.5) für d2 oder einen Kompilierungsfehler   tritt auf.

Meine Frage ist, was die Motivation für diese Kompilierzeitprüfung im speziellen Fall von statischen Methoden war, ein Beispiel dafür, dass der Fehler, diese Überprüfung während der Kompilierung durchzuführen, Probleme hätte, wäre ideal.

    
Jaime Hablutzel 16.06.2015, 18:10
quelle

3 Antworten

8

Dies ist eines der bizarrsten Dinge in Java. Sagen wir, wir haben die folgenden 3 Klassen

%Vor%

Nehmen wir an, alle 3 Klassen stammen von verschiedenen Anbietern mit unterschiedlichen Veröffentlichungsterminen.

Zur Kompilierzeit von C weiß der Compiler, dass die Methode B.foo() tatsächlich von A stammt und die Signatur foo()->Number . Der generierte Bytecode für den Aufruf verweist jedoch nicht auf A ; Stattdessen referenziert es die Methode B.foo()->Number . Beachten Sie, dass der Rückgabetyp Teil der Methodenreferenz ist.

Wenn JVM diesen Code ausführt, sucht er zuerst nach der Methode foo()->Number in B ; Wenn die Methode nicht gefunden wird, wird die direkte Superklasse A gesucht und so weiter. A.foo() wird gefunden und ausgeführt.

Jetzt beginnt die Magie - Bs Hersteller gibt eine neue Version von B frei, die "überschreibt" A.foo

%Vor%

Wir haben die neue Binärdatei von B bekommen und führen unsere App erneut aus. (Beachten Sie, dass die Binärdatei C gleich bleibt; sie wurde nicht mit der neuen B neu kompiliert.) Tada! - C.x ist jetzt 0.2f zur Laufzeit !! Weil JVMs Suche nach foo()->Number diesmal in B endet.

Dieses magische Feature verleiht den statischen Methoden einen gewissen Grad an Dynamik. Aber wer braucht diese Funktion, ehrlich? Wahrscheinlich niemand. Es erzeugt nichts als Verwirrungen, und sie wünschen sich, dass sie es entfernen könnten.

Beachten Sie, dass die Suchmethode nur für eine einzelne Kette von Eltern funktioniert - deshalb hat Java8, als sie statische Methoden in Interfaces eingeführt haben, entscheiden müssen, dass diese statischen Methoden nicht von Subtypen vererbt werden.

Lasst uns ein wenig weiter in dieses Kaninchenloch gehen. Angenommen, B gibt eine weitere Version mit "kovariant return type"

frei %Vor%

Dies kompiliert gegen A , soweit B weiß. Java erlaubt es, weil der Rückgabetyp "kovariant" ist; Diese Funktion ist relativ neu. zuvor muss die statische Methode "overriding" den gleichen Rückgabetyp haben.

Und was wäre C.x diesmal? Es ist 0.1f ! Weil JVM foo()->Number in B nicht findet; Es ist in A gefunden. JVM berücksichtigt ()->Number und ()->Integer als 2 verschiedene Methoden, wahrscheinlich um einige Java-fremde Sprachen zu unterstützen, die auf JVM ausgeführt werden.

Wenn C für diese neueste B neu kompiliert wird, referenziert C's binary B.foo()->Integer ; Zur Laufzeit wird C.x 42 sein.

Nun entschließt sich der Verkäufer von B, nachdem er all die Beschwerden gehört hat, foo aus B zu entfernen, weil es so gefährlich ist, statische Methoden "außer Kraft zu setzen". Wir erhalten die neue Binärdatei von B und führen C erneut aus (ohne C neu zu kompilieren) - boom, Laufzeitfehler, weil B.foo()->Integer nicht in B oder in A gefunden wird.

Dieses ganze Durcheinander weist darauf hin, dass es eine Design-Übersicht war, dass statische Methoden "kovariante Rückgabetypen" haben konnten, die eigentlich nur für Instanzmethoden gedacht sind.

UPDATE - Diese Funktion mag in einigen Anwendungsfällen reizvoll sein, zum Beispiel bei statischen Factory-Methoden - A.of(..) gibt A zurück, während B.of(..) eine spezifischere B zurückgibt. Die API-Designer müssen vorsichtig und vernünftig über potenzielle gefährliche Anwendungen sein. Wenn A und B vom selben Autor stammen und nicht von Benutzern unterklassifiziert werden können, ist dieses Design ziemlich sicher.

    
ZhongYu 16.06.2015, 23:53
quelle
1

String ist kein Untertyp von void . Nun zur eigentlichen Frage:

Der Kern dieser Einschränkung ist die Tatsache, dass static -Methoden in Java vererbt werden, aber nicht überschrieben werden können. Wenn wir die Motivation für die Kompilierung der Rückgabetypen von static -Methoden finden müssen, lautet die eigentliche Frage: Warum werden statische Methoden in Java geerbt, können aber nicht überschrieben werden?

Die Antwort ist einfach.

static Methoden können nicht overriden sein, da sie zu einem class und nicht zu einem instance gehören. Wenn Sie die Motivation dahinter wissen wollen, können Sie sich ansehen diese Frage, die schon einige Antworten hat. static Methoden dürfen vererbt werden, da es unzählige Situationen gibt, in denen eine Unterklasse eine static -Methode wiederverwenden möchte, ohne denselben Code wiederholen zu müssen. Stellen Sie sich ein Beispiel vor, bei dem die Anzahl der Instanzen einer Klasse gezählt wird:

%Vor%

Parent weiß, wie die Anzahl der instances , die von sich selbst erstellt wurden, gezählt wird. Child ist ein Parent , also sollte Child idealerweise wissen, wie Instanzen von sich selbst gezählt werden. Wenn static -Member nicht vererbt würden, müssten Sie den Code in Parent ebenfalls in Child duplizieren.

    
CKing 16.06.2015 18:30
quelle
0

Ich sehe nichts falsch daran, wie der Compiler Sie behandelt hat, ob Ihre Methoden statisch oder anders sind. Nur die nächsten paar Zeilen in der Dokumentation sagen:

  

Diese Regel erlaubt kovariante Rückgabetypen - verfeinert den Rückgabetyp einer Methode beim Überschreiben.   Wenn R1 kein Untertyp von R2 ist, wird eine unkontrollierte Kompilierungswarnung angezeigt   sofern nicht durch die Annotation SuppressWarnings unterdrückt (§9.6.3.5).

Löschen. R1 muss ein Subtyp von R2 sein wie Integer zu einem anderen Integer oder zu Number . String ist kein Subtyp von void .

Dokumentation sagt: "Kompilierungszeit ungeprüfte Warnung tritt auf ...". Aber was ich bemerkt habe ist, dass ein vollwertiger Kompilierungsfehler erwartet wird.

Die Vererbung funktioniert immer noch wie erwartet. Entfernen Sie Ihre untergeordnete Methode, und Sie haben Zugriff auf die statischen oder anderen Elemente des übergeordneten Elements.

    
mohsenmadi 16.06.2015 18:40
quelle

Tags und Links