9.3 Nichtstatische geschachtelte Typen
Ein nichtstatischer geschachtelter Typ ist ein innerer Typ vergleichbar, mit einer Objekteigenschaft. Deklarieren wir eine innere Klasse Room in House:
class House {
private String owner = "Ich";
class Room {
void ok() {
System.out.println( owner );
}
// static void error() { }
}
}
Ein Exemplar der Klasse Room hat Zugriff auf alle Eigenschaften von House, auch auf die privaten. Eine wichtige Eigenschaft ist, dass innere Klassen selbst keine statischen Eigenschaften deklarieren dürfen. Der Versuch führt in unserem Fall zu einem Compilerfehler: »The method error cannot be declared static; static methods can only be declared in a static or top level type«.
9.3.1 Exemplare innerer Klassen erzeugen
Um ein Exemplar von Room zu erzeugen, muss ein Exemplar der äußeren Klasse existieren. Das ist eine wichtige Unterscheidung gegenüber den statischen geschachtelten Klassen aus Abschnitt 9.2, »Statische geschachtelte Typen«: Statische geschachtelte Typen existieren auch ohne Objekt der äußeren Klasse.
In einem Konstruktor oder in einer Objektmethode der äußeren Klasse kann einfach mit dem Schlüsselwort new ein Exemplar der inneren Klasse erzeugt werden. Kommen wir von außerhalb – oder von einem statischen Block der äußeren Klasse – und wollen wir Exemplare der inneren Klasse erzeugen, so müssen wir bei nichtstatischen geschachtelten Klassen sicherstellen, dass es ein Exemplar der äußeren Klasse gibt. Java schreibt eine spezielle Form für die Erzeugung mit new vor, die folgendes allgemeine Format besitzt:
referenz.new InnereKlasse(...)
Dabei ist referenz eine Referenz vom Typ der äußeren Klasse. Um in der statischen main(String[])-Methode des Hauses ein Room-Objekt aufzubauen, schreiben wir:
House h = new House();
Room r = h.new Room();
Oder auch in einer Zeile:
Room r = new House().new Room();
9.3.2 Die this-Referenz
Möchte eine innere Klasse In auf die this-Referenz der sie umgebenden Klasse Out zugreifen, schreiben wir Out.this. Wenn Variablen der inneren Klasse die Variablen der äußeren Klasse überdecken, so schreiben wir Out.this.Eigenschaft, um an die Eigenschaften der äußeren Klasse Out zu gelangen:
class FurnishedHouse {
String s = "House";
class Room {
String s = "Room";
class Chair {
String s = "Chair";
void output() {
System.out.println( s ); // Chair
System.out.println( this.s ); // Chair
System.out.println( Chair.this.s ); // Chair
System.out.println( Room.this.s ); // Room
System.out.println( FurnishedHouse.this.s ); // House
}
}
}
public static void main( String[] args ) {
new FurnishedHouse().new Room().new Chair().output();
}
}
[»] Hinweis
Nichtstatische geschachtelte Klassen können beliebig geschachtelt sein, und da der Name eindeutig ist, gelangen wir mit Klassenname.this immer an die jeweilige Eigenschaft.
Betrachten wir das obige Beispiel, dann lassen sich Objekte für die inneren Klassen Room und Chair wie folgt erstellen:
FurnishedHouse h = new FurnishedHouse(); // Exemplar von FurnishedHouse
FurnishedHouse.Room r = h.new Room(); // Exemplar von Room in h
FurnishedHouse.Room.Chair c = r.new Chair(); // Exemplar von Chair in r
c.out(); // Methode von Chair
Die Qualifizierung mit dem Punkt bei FurnishedHouse.Room.Chair bedeutet nicht automatisch, dass FurnishedHouse ein Paket mit dem Unterpaket Room ist, in dem die Klasse Chair existiert. Die Doppelbelegung des Punktes verbessert die Lesbarkeit nicht gerade, und es droht Verwechslungsgefahr zwischen inneren Klassen und Paketen. Deshalb sollte die Namenskonvention beachtet werden: Klassennamen beginnen mit Großbuchstaben, Paketnamen mit Kleinbuchstaben.
9.3.3 Vom Compiler generierte Klassendateien *
Für das Beispiel House und Room erzeugt der Compiler die Dateien House.class und House$Room.class. Damit die innere Klasse an die Attribute der äußeren gelangt, generiert der Compiler automatisch in jedem Exemplar der inneren Klasse eine Referenz auf das zugehörige Objekt der äußeren Klasse. Damit kann die innere Klasse auch auf nichtstatische Attribute der äußeren Klasse zugreifen. Für die innere Klasse ergibt sich folgendes Bild in House$Room.class:
Die Variable this$0 referenziert das Exemplar House.this, also die zugehörige äußere Klasse. Die Konstruktoren der inneren Klasse erhalten einen zusätzlichen Parameter vom Typ House, um die this$0-Variable zu initialisieren. Da wir die Konstruktoren sowieso nicht zu Gesicht bekommen, kann uns das egal sein.
9.3.4 Erlaubte Modifizierer bei äußeren und inneren Klassen
Ist in einer Datei nur eine Klasse deklariert, kann diese nicht privat sein. Private innere Klassen sind aber legal. Statische Hauptklassen gibt es zum Beispiel auch nicht, aber innere statische Klassen sind legitim. Tabelle 9.2 fasst die erlaubten Modifizier noch einmal kompakt zusammen:
Modifizierer | äußeren | inneren | äußeren | inneren |
---|---|---|---|---|
public | ja | ja | ja | ja |
protected | nein | ja | nein | ja |
private | nein | ja | nein | ja |
static | nein | ja | nein | ja |
final | ja | ja | nein | nein |
abstract | ja | ja | ja | ja |