4.13.4 | Elementklassen |
Elementklassen werden wie Methoden und Datenelemente als Bestandteile einer Klasse vereinbart. Im Unterschied zu geschachtelten Top-Level-Klassen werden sie aber nicht als static definiert und dürfen keine static-Bestandteile vereinbaren:public class Outer { public class Inner {} }
So wie eine Exemplarvariable oder eine Exemplarmethode immer fest zu einem Exemplar gehört, gehört zu den Exemplaren einer Elementklasse stets ein Exemplar der umschließenden Klasse. Auch die Elementklassen können über mehrere Ebenen geschachtelt werden. In diesem Fall gibt es entsprechend eine Kette von umschließenden Exemplaren. Analog spricht man von den umschließenden Klassen einer Elementklasse.
Exemplare der Elementklassen können auf alle Datenelemente, auch die privaten der umschließenden Exemplare, zugreifen. Die Grundlage für diese Zugriffe schafft wiederum der Compiler: Er versieht eine Elementklasse mit einem privaten Datenelement, das zur Referenzierung des umschließenden Exemplars dient. Zur Initialisierung dieser Referenz wird allen Konstruktoren der Elementklasse intern ein weiterer Parameter hinzugefügt, der bei der Erzeugung mit einer Referenz auf das umschließende Exemplar belegt wird.
Weiterhin fügt der Compiler Zugriffsmethoden in die umschließenden Klassen ein, mit denen die Elementklasse auch auf die privaten Bestandteile der umschließenden Klassen und anderen Elementklassen zugreifen kann. Auch umgekehrt kann die umschließende Klasse auf private-Bestandteile der Elementklassen zugreifen.
Elementklassen werden für Hilfsklassen benutzt, die nicht nach außen hin verfügbar sein sollen oder von denen nur dann Exemplare erzeugt werden können sollen, wenn es auch ein Exemplar der umschließenden Klasse gibt. Sie sind auch dann zu verwenden, wenn die geschachtelte Klasse auf Exemplarvariablen der umschließenden Klasse zugreifen muss, was mit geschachtelten Top-Level-Klassen nicht möglich ist.
Den Zugriff auf ein Datenelement des umschließenden Exemplars aus einer Elementklasse heraus demonstriert das folgende Beispiel. Es zeigt eine Klasse WordCounter, die eine Häufigkeitstabelle für die Wörter einer Textdatei erstellt. Sie hat eine Methode print(), mit der die Tabelle nach dem Zählen in einen Stream ausgegeben werden kann. Eine Referenz auf diesen Stream ist im Datenelement writer gespeichert.public class WordCounter { ... public WordCounter(Reader reader, PrintWriter writer) { this.reader = reader; this.writer = writer; ... } public void print() { Collection c = wordTable.values(); Iterator it = c.iterator(); while(it.hasNext()) { Counter counter = (Counter)it.next(); counter.dump(); } } ... class Counter { String word; int count; public Counter(String word) { this.word = word; count = 1; } public void inc() { count++; } public void dump() { // Zugriff auf Datenelement des // umschließenden Exemplars writer.println(word+": "+count); } } ... }Material zum Beispiel
Für jedes Wort wird beim ersten Vorkommen ein Exemplar der Elementklasse Counter erzeugt, die einen Häufigkeitszähler für ein einzelnes Wort darstellt. Zur Ausgabe der Häufigkeitstabelle ruft die erwähnte Methode print() die Methode dump() aller vorhandenen Zählerobjekte auf. dump() greift zur Ausgabe des Worts und seiner Häufigkeit auf das Datenelement writer des umschließenden Exemplars zu.
- Quelltexte:
Im Abschnitt 16.2.6 wird dieses Beispiel im Zusammenhang mit den in Version 1.2 eingeführten Container-Klassen noch einmal aufgegriffen.
Elementklassen können wie Datenelemente und Methoden auch mit einer der Zugriffsklassen public, protected und private versehen werden. Die Modifier final und abstract sind bei Elementklassen ebenfalls erlaubt.
Aus folgenden Gründen ist eine public-Elementklasse nicht dasselbe wie eine geschachtelte Top-Level-Klasse:
- Geschachtelte Top-Level-Klassen sind nicht mit umschließenden Exemplaren assoziiert.
- Durch die fehlenden umschließenden Exemplare kann eine geschachtelte Top-Level-Klasse nur auf die statischen Bestandteile der umschließenden Klassen zugreifen. Für Elementklassen dagegen sind auch die Exemplarvariablen zugänglich.
Da Exemplare von Elementklassen immer mit einem umschließenden Exemplar assoziiert sind, waren syntaktische Ergänzungen für die Verwendung von this, new und super erforderlich, die in den folgenden drei Abschnitten beschrieben werden.
Datenelemente einer Elementklasse verdecken die Datenelemente der umschließenden Klasse, falls die Bezeichner übereinstimmen. Dies steht in Analogie zur Verdeckung von Datenelementen durch Methodenparameter, die im Abschnitt 4.3.2 behandelt wurde. Zusätzlich können bei Elementklassen auch Methoden der umschließenden Klassen verdeckt werden.
Im Bedarfsfall können die verdeckten Datenelemente und Methoden der umschließenden Klassen mit this referenziert werden, dessen Syntax für diesen Zweck so erweitert wurde, dass beliebige umschließende Exemplare referenziert werden können. Hierzu wird this der Name der entsprechenden umschließenden Klasse vorangestellt. Da ein Klassenname innerhalb einer Schachtelung eindeutig sein muss, ist auch die this-Referenz stets eindeutig.public class A { int x; class B { // Datenelement x von A wird verdeckt. int x; B() { // Zugriff mit this A.this.x = 0; } } }
Mit der neuen Syntax-Erweiterung kann man dem new-Operator durch einen Punkt getrennt eine Referenz auf das Objekt voranstellen, das zum umschließenden Exemplar werden soll:public class Outer { public class Inner {} } ... Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();In der zweiten Zeile wird outer zum umschließenden Exemplar von inner.
Das Beispiel zeigt auch, dass ein Exemplar einer Elementklasse auch außerhalb der umschließenden Exemplare referenziert werden kann, sofern ihre Zugriffsklasse dies gestattet.
Ohne diesen Zusatz wird das neue Objekt standardmäßig dem Exemplar zugeteilt, das das Objekt erzeugt. Der AufrufInner i = new Inner();darf daher nur innerhalb der Klasse Outer oder einer darin geschachtelten Elementklasse stehen, ansonsten liefert der Compiler eine Fehlermeldung, da das erzeugte Objekt keinem passenden umschließenden Exemplar zugeordnet werden kann.
Die Erweiterung der Syntax von super ist dadurch erforderlich geworden, dass es prinzipiell möglich ist, eine Klasse von einer Elementklasse abzuleiten. Obwohl dies in der Praxis kaum vorkommen dürfte, muss man konsequenterweise auch super eine Referenz voranstellen können, wie das folgende Beispiel zeigt:class Outer { // Eine Elementklasse public class Inner {} } // Unterklasse der Elementklasse public class Demo extends Outer.Inner { // Im Konstruktor muss ein umschliessendes // Exemplar angegeben werden. public Demo(Outer o) { // voranstellen der Referenz o.super(); } } ... Outer outer = new Outer(); Demo demo = new Demo(outer);Hierbei darf man sich nicht davon irritieren lassen, dass Demo eine Top-Level-Klasse ist. Da Demo von der Elementklasse Inner abgeleitet ist, muss es zu allen Exemplaren von Demo auch ein umschließendes Exemplar der Klasse Outer geben. Somit muss bei der Erzeugung eines neuen Demo-Objekts auch ein umschließendes Exemplar analog zum vorhergehenden Abschnitt spezifiziert werden, indem man dem Konstruktoraufruf super() eine entsprechende Referenz voranstellt. Unterlässt man dies, meldet der Compiler einen Fehler. Genauso resultiert eine Fehlermeldung, wenn man keinen Konstruktor in der Unterklasse definiert, weil der voreingestellte Konstruktor keine Verbindung zu einem umschließenden Exemplar herstellt.