2015-03-24 8 views
10

Diese Klasse:Warum kann eine Klasse eine darin enthaltene statische geschachtelte Klasse nicht erweitern?

public class OuterChild extends OuterChild.InnerParent { 
    public static class InnerParent { 
    } 
} 

Fails zu kompilieren:

$ javac OuterChild.java 
OuterChild.java:1: error: cyclic inheritance involving OuterChild 
public class OuterChild extends OuterChild.InnerParent { 
    ^
1 error 

weil OuterChild würde sich "abhängen", weil (pro §8.1.4 "Superclasses and Subclasses" of The Java Language Specification, Java SE 8 Edition) eine Klasse auf jeder Art direkt abhängt, dass „in die Rede ist [ its] extends oder implements clause [& hellip;] als Qualifier in der vollqualifizierten Form eines Superklassen- oder Superinterface-Namens. "

Aber ich verstehe die Motivation hier nicht wirklich. Was ist die problematische Abhängigkeit? Ist es nur für die Konsistenz mit dem Fall, in dem InnerParent nicht static war (und würde daher mit einer lexikalischen umschließenden Instanz von sich selbst enden)?

+2

@ downvoter: Bitte erklären warum? – ruakh

Antwort

5

Dies scheint ein ziemlich schändlicher Eckfall zu sein, da es eine number of bugs im Zusammenhang mit zyklischer Vererbung gibt, die oft zu Endlosschleifen, Stapelüberläufen und OOMs im Compiler führt. Hier sind einige relevante Zitate, die einen kleinen Einblick bieten können:

Bug 4326631:

Dieses Beispiel ist nicht legal, und dies wird deutlich gemacht, in der kommenden 2. Ausgabe der Java Language Specification. Klassen gleichzeitig durch Vererbung und Gehäuse verwandt sind problematisch, jedoch der ursprüngliche innere Klassen Whitepaper nicht angemessen das Problem, noch die pre-1.3-Compiler eine konsistente Richtlinie implementieren. In JLS 2nd Ausgabe wurde die Regel gegen zyklische Vererbung erweitert, um eine Klasse oder Schnittstelle von "abhängig" auf sich selbst, direkt oder indirekt zu verbieten. Ein Typ hängt nicht nur von Typen ab, die er erweitert oder implementiert, sondern auch von Typen, die als Qualifier innerhalb der Namen dieser Typen dienen.

Bug 6695838:

Die beiden Klassendeklarationen sind tatsächlich zyklischen; entsprechend JLS 8.1.4 haben wir das:

Foo hängt von Foo $ Intf (Foo $ Intf erscheint in der Geräte-Klausel von Foo)
Foo $ Intf hängt von Moo $ Intf (Moo $ Intf erscheint in der sich Klausel von Foo $ Intf)
Foo $ Intf hängt von Foo ab (Foo erscheint als Qualifier in der extend-Klausel von Foo $ Intf)

Für Transitivität haben wir, dass Foo von sich selbst abhängt; Daher sollte der Code mit einem Kompilierungsfehler zurückgewiesen werden.

Bug 8041994:

Schritt zurück, die direkt-depends Beziehung für Klassen und Schnittstellen in JLS2 eingeführt wurden JLS1 zu klären und Oberklassen/Superschnitt abzudecken, die Klassen verschachtelt sind (zB AB in der Beschreibung) .

Bug 6660289:

Dieses Problem der Ordnung zurückzuführen ist, in dem Javac Zuschreibung von Typ-variable attribution Grenzen wrt Klasse auszuführen.

1) Zuordnung der Klasse Outer < T Outer.Inner erstreckt>
1a) Zuordnung von Outer triggert Zuschreibung des Typs der Outer variable
2) Zuordnung von Outer.T
2a) Zuordnung von Outer.T triggert attribution seiner erklärt
3) Zuordnung der Klasse Outer.Inner < S erstreckt T>
3a) Zuordnung von Outer.Inner triggert Zuschreibung von Outer.Inner des Typs Variable
4) Zuordnung von Outer.Inner < S gebunden>
4a) Namensnennung r.Inner.S löst die Attributierung seiner deklarierten Grenze aus
5) Zuordnung von Outer.T - dies tut nichts anderes, als den Typ von T zurückzugeben; Wie Sie sehen können, wurde zu diesem Zeitpunkt die Grenze von T noch nicht auf dem Objekt festgelegt, das den Typ von T repräsentiert. Zu einem späteren Zeitpunkt führt javac für jede attributierte Typvariable eine Überprüfung durch, um sicherzustellen, dass die Grenze von a Die angegebene Typvariable führt keine zyklische Vererbung ein. Aber wir haben gesehen, dass für Outer.T keine Grenze gesetzt ist; Aus diesem Grund stürzt javac mit einer NPE ab, wenn versucht wird, einen Zyklus im Vererbungsbaum zu erkennen, der durch die angegebene Grenze von Outer.Inner.S ausgelöst wird.

Bug 6663588:

Typ-variable Grenzen zu Klassen gehören, zu einem zyklischen Vererbungsbaum beziehen könnten, die den Lösungsprozess bewirkt eine Schleife einzutreten, wenn für die Symbole nach oben.

Um Ihre spezifische Frage der „was die problematische Abhängigkeit ist?“ es scheint ein komplexer Kompilierung-Symbol Auflösung Rand Fall zu sein, und die Lösung in JLS2 eingeführt war einfach Zyklen durch Qualifier Typen eingeführt zu verbieten sowie tatsächliche Supertypen.

Mit anderen Worten könnte dies theoretisch gemacht werden, um mit entsprechenden Verbesserungen des Compilers zu arbeiten, aber bis jemand kommt und dies geschieht, ist es praktischer, diese ungewöhnliche Beziehung nur in der Sprachspezifikation zu verbieten.

+0

Danke! Ich habe jetzt die Fehler gelesen, die Sie gefunden haben, und obwohl sie nicht explizit etwas sagen wie "es war schwer, also haben wir aufgegeben", stimme ich Ihrer Einschätzung zu, dass dies der Unterton zu sein scheint. :-P – ruakh

+0

Ja, ich suchte nach etwas im JLS2-Dokumentations-/Entwicklungsprozess, das den Grund für die Änderung erklärte, aber nichts explizit fand. Ich stelle mir vor, dass das Abschleppen der entsprechenden Mailinglisten etwas wie "Ugh, lass es uns verbieten und fertig damit sein!" :) – dimo414

2

Ein gebildeter SWAG: Weil die JVM zuerst die Elternklasse laden muss, die einen Befehl zum Laden der inneren Klasse enthält. Die innere Klasse wird definiert durch CL nach Die äußere Klasse ist definiert, so dass alle Referenzen auf die Felder oder Methoden der äußeren Klasse auflösbar sind. Indem versucht wird, das Äußere durch das Innere zu erweitern, fordert es die JVM auf, das Innere vor dem Äußeren zu kompilieren, wodurch ein Problem mit Hühnern und Eiern entsteht. Das Problem hat seine Wurzeln in der Tatsache, dass eine innere Klasse die Feldwerte ihrer äußeren Klasse referenzieren kann, vorausgesetzt, dass Regeln bezüglich Umfang und Instanziierung (statisch v. Nicht-statisch) befolgt werden. Aufgrund dieser Möglichkeit müsste die JVM garantiert werden, dass zu keinem Zeitpunkt irgendetwas in der inneren Klasse versuchen wird, auf Feld- oder Objektreferenzen in der äußeren Klasse zuzugreifen oder diese zu mutieren. Es kann nur herausfinden, indem Sie beide Klassen, äußere zuerst, kompilieren, aber diese Information vorher zur Kompilierung benötigt, um sicher zu sein, dass es kein Bereichs- oder Instanzproblem irgendeiner Art geben wird. Es ist also ein Catch-22.

+1

Beachten Sie, dass dieses Problem bei der Kompilierung und nicht bei der Laufzeit auftritt und somit nichts mit der Klasseninitialisierungsreihenfolge der JVM zu tun hat. In der Tat, wenn die JVM feststellt, dass sie eine statische innere Klasse initialisieren muss, muss sie * nicht * zuerst die äußere Klasse initialisieren - siehe [JLS §12.4.1] (https://docs.oracle.com/javase/specs/ jls/se8/html/jls-12.html # jls-12.4.1). – dimo414

Verwandte Themen