Start / Seminare / Java SE Fortgeschrittene Sprachfeatures

Modul

Innere Typen

5 Kapitel in diesem Modul-Video · Laufzeit

Vertonung mit synthetischer Stimme · Inhalte redaktionell verantwortet · © 2026 HECKER CONSULTING, alle Rechte vorbehalten

Transkript

Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.

Innere Typen

0:00 In diesem Modul geht es um ein Sprachmittel, das fast jeder benutzt und kaum jemand genau kennt: Typen, die innerhalb anderer Typen stehen. Der Grund, warum sich der genaue Blick lohnt, ist ein einzelnes Schlüsselwort. Ob Sie static davorschreiben oder nicht, entscheidet darüber, ob Ihr Objekt heimlich ein zweites festhält — mit Folgen für Speicherverbrauch und Serialisierung, die erst im Betrieb auffallen.

0:22 Ich habe dieses Problem in echten Projekten gesehen, und die Fehlersuche dauerte Tage. Am Ende dieses Moduls werden Sie den Unterschied nicht nur kennen, sondern erklären können.

Innere Typen

0:33 Wir gehen in drei Schritten vor. Zuerst die Landkarte: Welche Typen lassen sich überhaupt verschachteln, und bei welchen stellt sich die Frage nach static gar nicht? Dann die beiden Fälle im Einzelnen — statisch verschachtelte Klassen am Beispiel eines Builders, innere Klassen am Beispiel einer Reservierung im Lager. Dazwischen liegt das Kapitel, in dem sich auflöst, was eine innere Klasse eigentlich ausmacht.

0:55 Und zum Schluss die anonyme innere Klasse, die Sie vermutlich schon oft geschrieben haben, ohne über ihre Eigenheiten nachzudenken. Vier Punkte, und der letzte ist der teuerste. Zunächst die Übersicht, damit wir dieselben Begriffe verwenden. Dann der praktische Nutzen statisch verschachtelter Klassen — der Builder ist dafür das Musterbeispiel. Dann die Bindung, die eine innere Klasse an ihre umgebende Instanz eingeht.

1:20 Und schließlich die Folgen dieser Bindung, insbesondere bei der Serialisierung. Wenn Sie aus diesem Modul einen Satz mitnehmen, dann diesen: Im Zweifel statisch. Die nichtstatische Variante ist die begründungspflichtige.

Überblick über verschachtelte Typen

1:34 Verschaffen wir uns zuerst einen Überblick. Das lohnt sich, weil die Begriffe in der Praxis munter durcheinandergehen — verschachtelt, innen, anonym, statisch. Wer sie vermischt, kann auch die Fehlermeldungen nicht deuten, denn die unterscheiden sehr genau. Wir sortieren also zunächst, welche Typen sich überhaupt verschachteln lassen und bei welchen sich die Frage nach static gar nicht stellt.

1:56 Danach wird der Rest des Moduls deutlich einfacher, weil wir dann über dieselben Dinge reden. Klassen, Schnittstellen, Enums und Records lassen sich nicht nur in einem Paket deklarieren, sondern auch innerhalb eines anderen Typs. Solche Typen heißen verschachtelte Typen. Und jetzt kommt die Unterscheidung, um die es geht: Bei Klassen ist entscheidend, ob sie das Schlüsselwort static tragen. Tun sie es nicht, spricht man von einer inneren Klasse.

2:22 Das ist kein Wortspiel — es sind zwei fundamental verschiedene Dinge, die nur zufällig ähnlich aussehen. Merken Sie sich also: verschachtelt ist der Oberbegriff, innere Klasse der Sonderfall ohne static. Diese Tabelle ist die wichtigste Folie des Moduls, und ihre Botschaft ist erfreulich kurz: Die ganze Frage betrifft ausschließlich Klassen.

2:42 Schnittstellen, Enums und Records sind verschachtelt immer statisch — nichtstatische Varianten davon gibt es nicht. Das vereinfacht Ihren Alltag erheblich, denn es bedeutet: Ein verschachtelter Record kann Ihnen dieses Problem gar nicht einbrocken. Bleibt die erste und zweite Zeile, und dort steht der Unterschied, um den sich alles Weitere dreht.

3:02 Kein Verweis nach außen auf der einen Seite, ein verborgener Verweis auf der anderen. Warum macht man das überhaupt? Der erste Grund ist Ordnung: Ein Typ, der nur zu einem anderen gehört, soll auch dort stehen — der äußere Name wirkt dann fast wie eine zusätzliche Paketebene. Der zweite Grund ist praktischer: Verschachtelte und umgebende Typen sehen gegenseitig ihre privaten Mitglieder.

3:25 Das erlaubt Konstruktionen, die sonst eine öffentliche Schnittstelle bräuchten — den Builder etwa, der einen privaten Konstruktor aufruft. Und der dritte Grund gilt nur für innere Klassen: Sie kommen zusätzlich an den Zustand ihrer umgebenden Instanz. Das ist mächtig und, wie wir sehen werden, nicht ganz gratis. Vier Punkte gleich zu Beginn.

3:46 Der erste ist eine Frage des Maßes: Verschachteln, um Dateien zu sparen, führt zu äußeren Klassen mit tausend Zeilen — der Gewinn an Ordnung kippt dann ins Gegenteil. Der zweite räumt mit einem Missverständnis auf, das wir eben angesprochen haben: Ein verschachtelter Record ist immer statisch, daran führt kein Weg vorbei.

4:04 Der dritte betrifft die Sichtbarkeit — ein verschachtelter Typ ist oft ein Implementierungsdetail und muss nicht öffentlich sein. Und der vierte ist Erfahrung: Drei Ebenen Verschachtelung liest niemand mehr gern.

Statisch verschachtelte Klassen

4:16 Beginnen wir mit dem einfacheren und häufigeren der beiden Fälle. Statisch verschachtelte Klassen sind unspektakulär in der Mechanik — es sind gewöhnliche Klassen, die zufällig innerhalb einer anderen stehen. Im Entwurf sind sie trotzdem außerordentlich nützlich, weil sie eine Zugehörigkeit ausdrücken und zugleich einen Zugriff ermöglichen, den es sonst nicht gäbe.

4:36 Das Standardbeispiel dafür kennen Sie wahrscheinlich schon aus eigenen Projekten: den Builder. An ihm lässt sich beides gut zeigen. Eine statisch verschachtelte Klasse ist eine ganz normale Klasse, die zufällig innerhalb einer anderen steht. Sie hält keinen Verweis auf eine Instanz der umgebenden Klasse — sie ist also eigenständig und könnte genauso gut in einer eigenen Datei stehen.

4:59 Was sie gewinnt, ist der gegenseitige Zugriff auf private Mitglieder und die sichtbare Zugehörigkeit. Genutzt wird sie für Typen, die eng zur äußeren Klasse gehören und ohne sie keinen Sinn ergeben. Ein Builder ist dafür das Musterbeispiel, denn er baut ja genau ein bestimmtes Objekt. Sehen wir uns die Struktur an. Außen der Artikel mit einem privaten Konstruktor, innen der Builder als statisch verschachtelte Klasse.

5:23 Die Setter-Methoden geben jeweils den Builder zurück — das ist der Trick, der die fließende Schreibweise möglich macht. Worauf es hier aber wirklich ankommt, steht in der zweiten Zeile: Der Konstruktor ist privat. Und genau deshalb muss der Builder innen stehen, denn nur von dort aus kommt er an diesen Konstruktor heran.

5:41 Das ist der eigentliche Gewinn dieser Konstruktion — nicht die Ordnung, sondern die Durchsetzung. So sieht die Verwendung aus. Der Punkt zwischen Artikel und Builder zeigt die Zugehörigkeit, und die Kette der Aufrufe liest sich fast wie ein Satz. Es gibt noch eine Verkürzung, die wenige kennen: Sie können Artikel.Builder direkt importieren, dann steht dort nur noch new Builder.

6:03 Der äußere Name wirkt dabei wie ein zusätzlicher Paketname. Ob Sie das tun, ist Geschmackssache — meine Empfehlung wäre, den qualifizierten Namen stehen zu lassen, solange die Zugehörigkeit dadurch sichtbar bleibt. Ein Nebenthema, das trotzdem wichtig ist: Verdeckung. Wenn die verschachtelte Klasse ein Mitglied mit demselben Namen trägt wie die umgebende, dann meint dieser Name im Inneren nur noch das eigene Mitglied.

6:28 Das äußere ist damit nicht verschwunden, es ist nur verdeckt — Sie erreichen es über den vorangestellten Namen der äußeren Klasse. Das klingt nach einer Randnotiz und ist im Alltag auch selten relevant. Wenn es aber auftritt, kostet es Zeit, weil der Code völlig unauffällig aussieht und trotzdem etwas anderes tut, als man erwartet.

6:47 Jetzt die Entscheidungshilfe. Statisch ist richtig, wenn der Typ fachlich zur äußeren Klasse gehört, aber deren Zustand nicht braucht. Beim Builder ist das offensichtlich — er soll ja gerade dann arbeiten, wenn es das fertige Objekt noch gar nicht gibt. Der zweite Grund ist ein technischer: Kein verborgener Verweis bedeutet auch kein ungewollter Anhang bei der Serialisierung.

7:09 Und daraus folgt die Faustregel, die ich Ihnen ans Herz legen möchte: Im Zweifel statisch. Nichtstatisch ist die Variante, für die Sie einen Grund brauchen — und den sollten Sie benennen können. In dieser Aufgabe verlagern Sie einen Hilfstyp dorthin, wo seine Kopplung sichtbar wird. Der Erfolg ist gut messbar: Der öffentliche Konstruktor verschwindet, und danach führt genau ein Weg zum Objekt.

7:32 Das ist mehr als Kosmetik — es ist eine Zusage an alle Aufrufer, dass ein Artikel nur in einem gültigen Zustand entstehen kann. Ergänzen Sie danach eine Prüfung in der build-Methode und überzeugen Sie sich, dass der Builder den privaten Konstruktor weiterhin sieht. Genau diese Sichtbarkeit ist der Grund für die Verschachtelung.

7:51 Der erste Punkt macht die ganze Konstruktion zunichte: Bleibt der öffentliche Konstruktor stehen, gibt es zwei Wege zum Objekt — und der eine umgeht Ihre Prüfungen. Der zweite ist die Verdeckung von eben, die im Builder besonders leicht passiert, weil dort dieselben Feldnamen naheliegen. Der dritte ist eine Gelegenheit, die man verpasst: Ein Builder in einer eigenen Datei funktioniert, verschenkt aber die Aussage über die Zugehörigkeit.

8:15 Und der vierte ist ein echter Fehler mit spätem Auffallen — wenn build kein neues Objekt liefert, teilen sich mehrere Aufrufe denselben Zustand.

Mit inneren Klassen arbeiten

8:23 Jetzt der zweite Fall, und ich gehe dabei bewusst anders vor als sonst: Ich zeige Ihnen zuerst ein Beispiel und erkläre die Mechanik erst danach. Der Grund ist, dass die Erklärung erst dann wirklich zündet, wenn man den Effekt einmal gesehen hat — vorher klingt sie nach einer Spitzfindigkeit. Unser Beispiel ist eine Reservierung im Lager, also ein Vorgang, den Sie aus jedem Shop und jedem Buchungssystem kennen.

8:46 Achten Sie beim Lesen darauf, was der Code alles nicht übergeben muss. Die Fachlichkeit ist schnell erzählt. Der Lagerbestand führt zwei Zuordnungen: was verfügbar ist und was reserviert ist. Legt ein Kunde etwas in den Warenkorb, wandert die Menge von der einen in die andere. Später wird daraus entweder ein echter Abgang, oder die Reservierung wird freigegeben und die Menge kehrt zurück.

9:09 Das ist ein alltägliches Muster, das Sie so oder ähnlich in jedem Shop und jedem Buchungssystem finden. Und die Reservierung selbst setzen wir hier als innere Klasse um — ohne static. Hier die Struktur. Außen der Bestand mit seinen beiden Zuordnungen, innen die Reservierung. Achten Sie auf das, was fehlt: Vor class Reservierung steht kein static.

9:29 Damit ist sie eine innere Klasse, und das hat eine unmittelbare Konsequenz — sie sieht die privaten Felder des Bestands, ohne dass irgendjemand sie ihr übergeben hätte. Das wirkt zunächst wie Magie, ist aber eine ganz konkrete Mechanik, die wir im nächsten Kapitel auflösen. Merken Sie sich für jetzt nur den Effekt: unmittelbarer Zugriff ohne Parameter.

9:51 Der Ablauf beim Reservieren ist einfach, aber die Reihenfolge zählt. Zuerst prüfen, ob überhaupt genug da ist — und zwar bevor irgendetwas verändert wird. Das ist der klassische Fehler: erst buchen, dann merken, dass es nicht reicht, und dann mühsam zurückrollen. Danach die Menge von der Verfügbarkeit abziehen und auf die Reservierung addieren. Für beides bietet sich merge an, weil es Anlegen und Aufaddieren in einem Aufruf erledigt.

10:17 Und am Ende geben Sie eine Reservierung zurück, die weiß, worauf sie sich bezieht. Diese Methode zeigt genau das, worum es in diesem Kapitel geht. Sie greift auf das Feld reserviert zu — und dieses Feld gehört gar nicht der Reservierung, sondern dem Lagerbestand. Kein Parameter, kein Konstruktorargument, kein Setter. Es steht einfach da.

10:37 Beachten Sie außerdem den Schalter am Anfang: Eine Reservierung darf nur einmal eingelöst werden, sonst stimmen die Bestände nicht mehr. Das Freigeben sieht ganz ähnlich aus, mit einem entscheidenden Unterschied — dort muss die Menge zurück in die Verfügbarkeit gebucht werden. Setzen Sie die drei Wege selbst um: Anlegen, Einlösen, Freigeben.

10:58 Die Fähigkeit, die Sie dabei üben, ist ungewöhnlich — Sie schreiben einen Typ, der auf dem Zustand seines Erzeugers arbeitet, ohne ihn übergeben zu bekommen. Der Hinweis in der Aufgabe ist der Prüfstein: Freigeben muss die Menge zurückbuchen, Einlösen nicht. Genau darin unterscheiden sich die beiden, und genau das ist erfahrungsgemäß die Stelle, an der Tests fehlen.

11:20 Prüfen Sie alle drei Wege, dann haben Sie die Fachlichkeit auch dokumentiert. Der erste Punkt ist der, den wir im Vorgehen schon angesprochen haben — ändern vor prüfen. Der zweite ist die doppelte Einlösung, die ohne Zustandsschalter möglich bleibt und die Bestände stillschweigend verfälscht. Der dritte ist die vergessene Rückbuchung beim Freigeben; auch das fällt erst bei der Inventur auf.

11:43 Und der vierte ist eine allgemeine Kapselungsfrage: Wer die Zuordnungen nach außen gibt, gibt die Kontrolle über den Zustand ab — und alle sorgfältigen Prüfungen laufen dann ins Leere.

Innere Klassen verstehen

11:54 Und jetzt die Auflösung. Warum kann die Reservierung auf Felder zugreifen, die ihr niemand gegeben hat? Es steht kein Parameter im Konstruktor, kein Setter, kein Zwischenspeicher — und trotzdem funktioniert es. Die Antwort ist kurz und im Grunde harmlos. Sie erklärt aber nicht nur den Komfort, sondern gleichzeitig alle unangenehmen Nebenwirkungen, die innere Klassen mit sich bringen: den höheren Speicherverbrauch, das Verhalten beim Serialisieren und den teuersten Tippfehler dieses Moduls.

12:22 Eine Instanz einer inneren Klasse existiert immer im Zusammenhang mit einer bestimmten Instanz der umgebenden Klasse. Sie trägt einen verborgenen Verweis auf diese Instanz — einen, den niemand geschrieben hat und den man im Quelltext nicht sieht. Über diesen Verweis läuft der ganze Zugriff. Das erklärt den Komfort, und es erklärt zugleich den Preis: Solange die Reservierung lebt, lebt auch der Lagerbestand. Sie halten ohne es zu wissen ein zweites Objekt fest.

12:49 Bei einem Lagerbestand ist das harmlos. Bei einem Dienst mit zwanzig Abhängigkeiten nicht. Diese Folie macht das Unsichtbare sichtbar. Die ersten beiden Zeilen bedeuten exakt dasselbe — die zweite schreibt nur aus, was die erste verschweigt: Lagerbestand-Punkt-this ist der verborgene Verweis. Noch deutlicher wird es unten. Die Schreibweise bestand-Punkt-new zeigt schwarz auf weiß, dass Sie das new auf einer bestimmten Instanz aufrufen.

13:15 Ohne äußere Instanz gibt es keine innere. Diese Schreibweise werden Sie im Alltag praktisch nie verwenden — aber wenn Sie sie einmal gesehen haben, vergessen Sie das Prinzip nicht mehr. Was folgt daraus praktisch? Beim Serialisieren wandert die umgebende Instanz mit in den Datenstrom — und mit ihr alles, woran sie hängt.

13:34 Für die Speicherbereinigung gilt Ähnliches: Die äußere Instanz kann nicht freigegeben werden, solange die innere lebt. In einem Dienst mit vielen Abhängigkeiten zieht ein einziges kleines Objekt so den halben Anwendungskontext nach. Und jetzt der Punkt, auf den alles hinausläuft: Ein vergessenes static erzeugt genau diesen Effekt.

13:53 Der Code sieht dabei völlig normal aus, übersetzt anstandslos und funktioniert — bis er es nicht mehr tut. Diese Gegenüberstellung können Sie sich als Merkhilfe mitnehmen, aber eigentlich brauchen Sie nur die erste Zeile. Alles andere folgt daraus. Kein Verweis heißt: Erzeugen ohne äußere Instanz, kein unmittelbarer Zugriff auf Instanzfelder, keine Überraschung beim Serialisieren.

14:17 Verweis vorhanden heißt: genau das Gegenteil, mit allen Bequemlichkeiten und allen Kosten. Wenn Sie in einem Code-Review vor einer verschachtelten Klasse stehen, ist das die einzige Frage, die Sie stellen müssen — braucht dieser Typ den Zustand des äußeren? Wenn nein: static. Diese Aufgabe ist bewusst ein Experiment statt einer Umsetzung.

14:37 Zeigen Sie sich selbst, dass die Reservierung ohne Bestandsobjekt nicht erzeugbar ist — und dass sie es mit static plötzlich ist. Setzen Sie das Schlüsselwort testweise davor und lesen Sie die Fehlermeldungen, die dann in den Methoden auftauchen. Sie sagen Ihnen genau, welche Zugriffe vom verborgenen Verweis abhingen. Das ist eine Erfahrung, die man einmal machen sollte, weil sie das Prinzip verankert.

15:00 Danach sehen Sie in fremdem Code sofort, worauf zu achten ist. Der erste Punkt ist der teuerste dieses Moduls, und er ist so banal wie folgenreich: static vergessen. Der zweite ist die Kombination aus innerer Klasse und Serialisierung, die ich vorhin geschildert habe. Der dritte ist eine Wahrnehmungsfrage — Aussen-Punkt-this ist kein exotischer Sonderfall, sondern die Regel; jeder Zugriff läuft darüber, nur meistens unsichtbar.

15:26 Und der vierte ist der Fall, der in Produktivsystemen wirklich weh tut: innere Klassen in langlebigen Zwischenspeichern. Dort halten sie die äußeren Objekte fest, solange der Zwischenspeicher lebt.

Anonyme innere Klassen

15:37 Zum Abschluss noch eine Sonderform, die Sie wahrscheinlich schon oft geschrieben haben, ohne über ihre Eigenheiten nachzudenken — Listener, Comparator, kleine einmalige Umsetzungen einer Schnittstelle. Anonyme innere Klassen sind bequem, weil sie Deklaration und Erzeugung in einen einzigen Ausdruck packen. Sie haben allerdings zwei Eigenheiten, die man kennen sollte: Sie können keinen Konstruktor haben, und sie stellen eine Bedingung an die Variablen ihrer Umgebung.

16:03 Beides sehen wir uns jetzt an. Eine anonyme innere Klasse wird in einem einzigen Ausdruck definiert und erzeugt. Sie schreiben new, dahinter den Typ, den sie umsetzt, und dann direkt einen Klassenrumpf in geschweiften Klammern. Was auf den ersten Blick verwirrt: Der Typ darf eine Schnittstelle sein, obwohl man Schnittstellen nicht instanziieren kann.

16:24 Sie instanziieren auch keine — Sie definieren an Ort und Stelle eine namenlose Klasse, die die Schnittstelle umsetzt, und erzeugen davon eine Instanz. Und weil diese Klasse keinen Namen hat, kann sie auch keinen Konstruktor haben. Konstruktoren heißen ja wie ihre Klasse. Hier dieselbe Reservierung wie vorhin, nur diesmal anonym. Bemerkenswert ist, was fehlt: die Felder für Nummer und Menge.

16:48 Die braucht diese Klasse nicht, denn sie greift direkt auf die Parameter der umgebenden Methode zu. Das ist die zweite Besonderheit anonymer Klassen — sie sehen nicht nur die Felder der umgebenden Instanz, sondern auch die lokalen Variablen und Parameter der Methode, in der sie stehen. Das spart Zeilen und macht den Code ausgesprochen kompakt. Es hat allerdings eine Bedingung, und die kommt jetzt.

17:11 Die Bedingung heißt: effektiv final. Die Variablen, auf die zugegriffen wird, müssen sich verhalten, als wären sie final — einmal zugewiesen und nie wieder geändert. Das Wort final müssen Sie nicht hinschreiben, das Verhalten genügt. Der Grund dahinter ist mechanisch: Die anonyme Klasse bekommt eine Kopie dieser Werte, und wenn sich das Original später ändert, würden beide auseinanderlaufen.

17:34 Java verhindert das lieber, als Ihnen eine Inkonsistenz zu bescheren. In der Praxis merken Sie das erst, wenn Sie eine Variable neu zuweisen — dann tauchen plötzlich Fehler an Stellen auf, die Sie gar nicht angefasst haben. Hier üben Sie eine Abwägung. Formulieren Sie die Reservierung als Schnittstelle und setzen Sie sie in der Methode anonym um.

17:55 Die Tests sollten unverändert durchlaufen — die Fachlichkeit ändert sich ja nicht, nur die Bauform. Und dann machen Sie den Versuch aus dem Hinweis: Weisen Sie den Parameter nummer innerhalb der Methode neu zu. Die Fehlermeldung, die Sie dann bekommen, erklärt effektiv final besser als jede Folie — weil sie Ihnen genau die Stellen zeigt, die davon abhingen.

18:15 Der erste Punkt ist genau der eben beschriebene Effekt — eine harmlos wirkende Neuzuweisung bricht die Klasse. Der zweite ist eine Frage des Maßes: Eine anonyme Klasse mit fünfzig Zeilen Rumpf ist keine mehr, sie ist eine versteckte Klasse ohne Namen. Ab einer gewissen Länge gehört sie ausgelagert und benannt. Der dritte wird gern vergessen — auch eine anonyme Klasse ist eine innere Klasse und trägt denselben verborgenen Verweis, mit allen Folgen von vorhin.

18:42 Und der vierte ist heute der häufigste: Wo eine funktionale Schnittstelle vorliegt, genügt ein Lambda. Fassen wir zusammen. Bei Schnittstellen, Enums und Records stellt sich die static-Frage nicht — sie sind verschachtelt immer statisch. Bei Klassen entscheidet sie alles: Statisch verschachtelte Klassen ordnen eng gekoppelte Typen, ohne eine Bindung einzugehen.

19:03 Innere Klassen tragen einen verborgenen Verweis auf ihre umgebende Instanz, mit Komfort auf der einen und Kosten auf der anderen Seite. Und anonyme Klassen sehen zusätzlich die Parameter ihrer Methode, sofern die effektiv final sind. Im nächsten Modul wird es anspruchsvoll — wir steigen in Generics ein.

Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →