Warum erhalten Erweiterungen innerer Klassen doppelte Klassenreferenzen?

9

Ich habe die folgende Java-Datei:

%Vor%

Ich kompilierte dann die Datei mit diesem Befehl:

%Vor%

Dies ist die Ausgabe:

%Vor%

Die erste innere Klasse hat ihr thisOuter -Feld und zeigt auf die Instanz von int foo . Das ist gut. Die zweite innere Klasse, die die erste erweitert, hat ein doppeltes Feld mit demselben Namen , das initialisiert wird, bevor der Konstruktor der Superklasse mit demselben Wert aufgerufen wird.

Der Zweck des obigen javap -Feldes besteht lediglich darin zu bestätigen, dass vererbte Felder der Oberklasse nicht in der this%code% -Ausgabe der Disassemblierung einer untergeordneten Klasse angezeigt werden.

Das erste %code% -Feld ist nicht privat, daher sollte InnerChild es verwenden können. Das zusätzliche Feld scheint nur Speicher zu verschwenden. (Ich habe es zuerst mit einem Speicheranalyse-Tool entdeckt.) Was ist sein Zweck und gibt es einen Weg, wie ich es loswerden kann?

    
Boann 31.12.2013, 01:27
quelle

2 Antworten

2

Die zwei Klassen sind möglicherweise keine inneren Klassen derselben Klasse (wenn Sie eine komplexe Hierarchie hatten), daher gibt es Fälle, in denen die beiden Referenzen anders wären.

Zum Beispiel:

%Vor%

InnerTwo hat einen Verweis auf Wrapper , InnerOne hat einen Verweis auf Outer.

Sie können es mit dem folgenden Java-Code versuchen:

%Vor%

Ich habe es als Snippet auf tryjava8 eingerichtet: Ссылка

%Vor%

Sie können sehen, dass die zwei getOuters() -Aufrufe auf ein anderes Objekt verweisen.

    
Tim B 31.12.2013 03:13
quelle
1

@ TimBs Antwort ist interessant, weil sie zeigt, wie subtil die äußeren Klassenreferenzen sein können, aber ich denke, ich habe einen einfacheren Fall gefunden:

%Vor%

In diesem Fall ist InnerChilds thisthisouter -Referenz vom Typ Child und nicht von Outer. Daher kann das% Co_de% -Feld nicht vom übergeordneten Element verwendet werden, da der Wert zwar identisch ist, aber der falsche Typ ist. Ich vermute, das ist der Ursprung des zusätzlichen Feldes. Dennoch glaube ich, dass Javac es eliminieren könnte, wenn InnerChild und Inner die selbe äußere Klasse haben, weil dann das Feld denselben Wert und denselben Typ hat. (Ich würde es als einen Fehler melden, aber sie beheben sie nie, und wenn es kein Fehler ist, geben sie sowieso keine Rückmeldung.)

Ich habe aber endlich ein paar ordentliche Workarounds gefunden.

Die einfachste Problemumgehung (an die ich nicht lange denken konnte) ist, die Memberklassen "statisch" zu machen und den Ref als äußeres Feld in der Basisklasse zu speichern:

%Vor%

Über das Feld this%code% können sowohl Inner als auch InnerChild Instanzelemente von Outer (auch private) abrufen. Das entspricht dem Code, den JavaC sowieso für die ursprünglichen nicht statischen Klassen generieren sollte.

Zweite Problemumgehung: Ich habe durch Zufall entdeckt, dass die Kindklasse einer nicht statischen Mitgliedsklasse statisch sein kann! All diese Jahre in Java wusste ich nie über die obskure Syntax, die diese Arbeit macht ... es ist nicht in den offiziellen Tutorials. Ich hätte es immer für unmöglich gehalten, außer dass Eclipse automatisch den Konstruktor für mich ausgefüllt hat. (Eclipse scheint diese Magie jedoch nicht wiederholen zu wollen ... Ich bin so verwirrt.) Wie auch immer, anscheinend wird es ein "qualifizierter Superklassenkonstruktoraufruf" genannt, und es ist beerdigt in der JLS in Abschnitt 8.8.7.1 .

%Vor%

Dies ist der vorherigen Problemumgehung sehr ähnlich, mit Ausnahme von Inner ist eine Instanzklasse. InnerChild vermeidet das doppelte %code% -Feld, weil es statisch ist. Inner stellt einen Getter zur Verfügung, so dass InnerChild auf den Verweis auf Outer zugreifen kann (falls gewünscht). Dieser Getter ist endgültig und nicht virtuell, daher ist es für die VM einfach, inline zu arbeiten.

Eine dritte Umgehungsmöglichkeit besteht darin, die äußere Klassenreferenz nur in InnerChild zu speichern und Inner durch eine virtuelle Methode darauf zugreifen zu lassen:

%Vor%

Dies ist wahrscheinlich weniger nützlich (?). Ein Vorteil ist, dass InnerChild einen einfacheren Konstruktoraufruf hat, da das ref zu seiner einschließenden Klasse normalerweise implizit an es übergeben wird. Es kann auch eine obskure Optimierung sein, wenn InnerChild eine andere einschließende Klasse (OuterChild, extending Outer) besitzt, die für den Zugriff auf Mitglieder von Outer benötigt wird, um Elemente von OuterChild zu verwenden. erfordert aber, dass Inner eine virtuelle Methode für den Zugriff auf Member von Outer aufruft.

In der Praxis sind all diese Problemumgehungen ein bisschen lästig, also sind sie es nur wert, wenn Sie viele tausend Instanzen solcher Klassen haben (was ich tue!).

Aktualisieren!

Gerade der "qualifizierte Superklassen-Konstruktor-Aufruf" ist ein großer Teil des Puzzles, warum Javac in erster Linie doppelte Felder erzeugt. Es ist möglich, dass eine Instanz von InnerChild, die Inneres erweitert, beide Member-Klassen von Outer eine andere umschließende Instanz haben als ihre übergeordnete Instanz als umschließende Instanz betrachtet :

%Vor%

Ausgabe:

%Vor%

Ich denke immer noch, dass Javac mit einiger Mühe das doppelte Feld im allgemeinen Fall eliminieren könnte, aber ich bin mir nicht sicher. Es ist sicherlich ein komplexeres Problem als ich zuerst dachte.

    
Boann 03.01.2014 17:51
quelle

Tags und Links