Java verschachtelte Klassen
Verschachtelte Klassen in Java: statische nested classes, inner classes, lokale Klassen und anonyme Klassen.
Java erlaubt es, eine Klasse innerhalb einer anderen Klasse zu deklarieren. Der Oberbegriff dafür ist nested class (verschachtelte Klasse). Java bietet vier Varianten, die sich danach unterscheiden, wo sie definiert sind und ob sie eine Referenz auf eine umschließende Instanz tragen:
| Variante | Deklarationsort | Trägt umschließendes this? | Kapitel |
|---|---|---|---|
| Statische nested class | Im Klassenrumpf, mit static | Nein | dieses Kapitel |
| Inner class | Im Klassenrumpf, ohne static | Ja | inner classes |
| Lokale Klasse | Im Methodenrumpf | Ja (bei nicht-statischer Methode) | lokale Klassen |
| Anonyme Klasse | Als spontane Unterklasse/Implementierung | Ja (in nicht-statischem Kontext) | anonyme Klassen |
Der Grund, eine verschachtelte Klasse einer separaten Top-Level-Klasse vorzuziehen, ist Scope — die verschachtelte Klasse ist nur im Kontext ihrer umschließenden Klasse sinnvoll und sollte dem Rest der Codebasis nicht zugänglich sein. Dieses Kapitel gibt einen Überblick; jede Variante wird in den folgenden Kapiteln ausführlich behandelt.
Warum überhaupt Klassen verschachteln?
Drei wesentliche Gründe:
- Logische Gruppierung. Ein
Map.Entryergibt nur im Kontext einerMapSinn. Die Verschachtelung macht diese Beziehung im Code offensichtlich. - Kapselung. Eine verschachtelte Klasse kann
privatesein, sodass nichts außerhalb der umschließenden Klasse auf sie verweisen kann. - Closures über umschließenden Zustand. Eine inner / lokale / anonyme Klasse kann die Felder der umschließenden Instanz und methodenlokale Variablen lesen — das ist die Grundlage von Event-Handlern, Iteratoren und vielen kleinen Adapter-Mustern.
Wenn keiner dieser Gründe zutrifft, schreibe eine Top-Level-Klasse.
Statische nested classes
Eine als static markierte Klasse innerhalb einer anderen Klasse ist eine statische nested class:
public class Outer {
static class Inner {
void hi() { System.out.println("hi"); }
}
}
Outer.Inner i = new Outer.Inner(); // instantiate directly
i.hi();Eine statische nested class ist im Grunde eine Top-Level-Klasse, die zufällig im Namespace von Outer lebt. Sie hat keine implizite Referenz auf eine Outer-Instanz — man kann sie nutzen, ohne jemals eine zu erstellen. Der einzige Unterschied zu einer Top-Level-Klasse ist der Scope: Outer.Inner ist der qualifizierte Name.
Das ist die Variante, die in den Teilen 5 und 6 bereits durchgehend verwendet wurde — jedes static class Foo {} in einem RunnableJava-Beispiel ist eine solche Klasse. Der Grund für static war, dass die Klassen aus einer static-Methode main heraus instanziiert werden konnten, ohne eine Outer-Instanz zu benötigen.
Inner classes (nicht-statisch verschachtelt)
Lässt man static weg, erhält man eine inner class. Die Instanz der inner class ist an eine Instanz der äußeren Klasse gebunden und trägt eine implizite Referenz darauf:
public class Outer {
int x = 1;
class Inner { // no static
int get() { return x; } // reads Outer's x through the implicit reference
}
}
Outer o = new Outer();
Outer.Inner i = o.new Inner(); // unusual syntax — bind to o
System.out.println(i.get()); // 1Die ungewöhnliche Syntax o.new Inner() wird verwendet, um eine inner-class-Instanz zu erstellen, die an eine bestimmte äußere Instanz gebunden ist. Inner classes können keine static-Member haben (ältere Regel; seit Java 16+ gelockert, um static-Member in inner classes zu erlauben). Ausführliche Behandlung in inner classes.
Lokale Klassen
Eine innerhalb eines Methodenrumpfs deklarierte Klasse ist lokal — sie ist auf diese Methode beschränkt:
public void run() {
class Step { int n; Step(int n) { this.n = n; } } // visible only in run()
Step s = new Step(5);
}Nützlich für kleine Hilfskonstrukte, die keine eigene Top-Level-Klasse oder auch keine klassenebene verschachtelte Klasse verdienen. Sie haben Zugriff auf final- (oder effektiv finale) Variablen der umschließenden Methode. Das Kapitel lokale Klassen enthält die vollständige Erläuterung.
Anonyme Klassen
Eine anonyme Klasse ist eine einmalige Unterklasse oder Interface-Implementierung, die direkt an der Verwendungsstelle inline definiert wird:
Runnable r = new Runnable() {
@Override
public void run() { System.out.println("hi"); }
};Das ist ein einzelner Ausdruck, der (1) eine neue Klasse definiert, die Runnable implementiert, (2) eine Instanz davon erstellt und (3) diese r zuweist. Die Klasse hat keinen Namen — sie existiert nur an dieser Stelle. Fast alle Anwendungsfälle für anonyme Klassen wurden in modernem Java durch Lambdas ersetzt, aber sie sind weiterhin gültig und gelegentlich nützlich. Siehe anonyme Klassen.
Die richtige Variante wählen
Ein kurzer Entscheidungsbaum:
- Benötigt die verschachtelte Klasse eine Referenz auf eine äußere Instanz? Wenn nein → statische nested class. Wenn ja → eine nicht-statische Variante.
- Wird sie nur innerhalb einer einzelnen Methode verwendet? Wenn ja → lokal oder anonym. Wenn nein → inner class.
- Ist es eine einmalige Unterklasse/Interface-Implementierung mit ein oder zwei Methoden? Wenn ja → Lambda (bevorzugt) oder anonyme Klasse (veraltet).
Statische nested classes sind bei weitem die häufigste Variante. Inner classes erscheinen bei Iterator-ähnlichen Adaptern. Lokale und anonyme Klassen sind in modernem Code seltener — Lambdas übernehmen die meisten ihrer Anwendungsfälle.
Benennung und Zugriff
Verschachtelte Klassen können jeden Zugriffsmodifikator tragen — public, private, protected, package-private — und die Modifikatorregeln sind dieselben wie für Top-Level-Member. Map.Entry ist public; ein private static class Node innerhalb einer LinkedList-Implementierung ist von außen unsichtbar.
Kompilierte verschachtelte Klassen erhalten $-getrennte Namen in den .class-Dateien: Outer$Inner, Outer$1. Diese sieht man gelegentlich in Stack Traces und Debuggern.
Ein ausgearbeitetes Beispiel
Was kommt als Nächstes?
Die nächsten drei Kapitel behandeln jede der nicht-statischen Varianten im Detail. Zunächst: inner classes — die allgemeinste Variante — eine Klasse, die an eine umschließende Instanz gebunden ist.