7.3 Mitglieds- oder Elementklassen
Eine Mitgliedsklasse (engl. member class), auch Elementklasse genannt, ist ebenfalls vergleichbar mit einem Attribut, nur ist sie nicht statisch (statische innere Klassen lassen sich aber auch als statische Mitgliedsklassen bezeichnen). Deklarieren wir eine innere Mitgliedsklasse Room in House:
Listing 7.2: com/tutego/insel/inner/House.java, Ausschnitt
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 Mitgliedsklassen 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
7.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 inneren Klassen aus Abschnitt 7.2. Statische innere Klassen existieren auch ohne Objekt der äußeren Klasse.
In einem Konstruktor oder in einer Objektmethode der äußeren Klassen kann einfach mit dem new-Operator 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 Elementklassen 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()-Methode vom Haus ein Room-Objekt aufzubauen, schreiben wir:
Listing 7.3: com/tutego/insel/inner/House.java, main()
House h = new House();
Room r = h.new Room();
Oder auch in einer Zeile:
Room r = new House().new Room();
7.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:
Listing 7.4: com/tutego/insel/inner/FurnishedHouse.java,FurnishedHouse
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 |
Elementklassen 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 Punkts 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.
7.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 nicht-statische Attribute der äußeren Klasse zugreifen. Für die innere Klasse ergibt sich folgendes Bild in House$Room.class:
class HouseBorder$Room
{
final House this$0;
House$Room( House house )
{
this$0 = house;
}
// ...
}
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.
7.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. Die folgende Tabelle fasst die erlaubten Modifizier noch einmal kompakt zusammen:
Modifizierer erlaubt auf | äußeren Klassen | inneren Klassen | äußeren Schnittstellen | inneren Schnittstellen |
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 |
7.3.5 Innere Klassen greifen auf private Eigenschaften zu

Es ist ein Sonderfall, dass innere Klassen auf private Eigenschaften der äußeren Klasse zugreifen können. Das folgende Beispiel soll das illustrieren:
Listing 7.5: com/tutego/insel/inner/NotSoPrivate.java, NotSoPrivate
public class NotSoPrivate
{
private static class Family { private String dad, mom; }
public static void main( String[] args )
{
class Node { private Node next; }
Node n = new Node();
n.next = new Node();
Family ullenboom = new Family();
ullenboom.dad = "Heinz";
ullenboom.mom = "Eva";
}
}
Eine Klasse Outsider, die in der gleichen Compilationseinheit (also Datei) definiert wird, kann schon nicht mehr auf NotSoPrivate.Family zugreifen und natürlich auch keine Klasse einer anderen Compilationseinheit.
Zugriffsrechte *
Eine innere Klasse kann auf alle Attribute der äußeren Klasse zugreifen. Da eine innere Klasse als ganz normale Klasse übersetzt wird, stellt sich allerdings die Frage, wie sie das genau macht. Auf öffentliche Variablen kann jede andere Klasse ohne Tricks zugreifen, so auch die innere. Und da eine innere Klasse als normale Klassendatei im gleichen Paket sitzt, kann sie ebenfalls ohne Verrenkungen auf paketsichtbare und protected-Eigenschaften der äußeren Klasse zugreifen. Eine innere Klasse kann jedoch auch auf private Eigenschaften zurückgreifen, eine Designentscheidung, die sehr umstritten ist und lange kontrovers diskutiert wurde. Doch wie ist das zu schaffen, ohne gleich die Zugriffsrechte des Attributs zu ändern? Der Trick ist, dass der Compiler eine synthetische statische Methode in der äußeren Klasse einführt:
class House
{
private String owner;
static String access$0( House house )
{
return house.owner;
}
}
Die statische Methode access$0() ist der Helfershelfer, der für ein gegebenes House das private Attribut nach außen gibt. Da die innere Klasse einen Verweis auf die äußere Klasse pflegt, gibt sie diesen beim gewünschten Zugriff mit, und die access$0()-Methode erledigt den Rest.
Für jedes von der inneren Klasse genutzte private Attribut erzeugt der Compiler eine solche Methode. Wenn wir eine weitere private Variable int size hinzunehmen, würde der Compiler ein int access$1(House) generieren.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.