
In diesem Artikel erfährst du:
- Was ist eine unbenannte Variable, und welchen Zweck erfüllt sie?
- Was sind unbenannte Patterns und unbenannte Pattern-Variablen, und welchen Zweck erfüllen sie?
- Wie kann man
switchmit unbenannten Pattern-Variablen prägnanter schreiben? - Wo ist der Unterstrich erlaubt, und wo nicht?
Unbenannte Variablen und Patterns wurden in Java 21 im Rahmen von Project Amber als Preview-Feature eingeführt. In Java 22 wurden sie finalisiert.
Unbenannte Variablen
Oft kommt es vor, dass wir eine Variable definieren müssen, die wir gar nicht benötigen. Hier zwei Beispiele, die du wahrscheinlich kennst:
Beispiel 1: Exceptions – hier wird e nicht verwendet:
try {
int number = Integer.parseInt(string);
} catch (NumberFormatException e) {
System.err.println("Not a number");
}
Beispiel 2: Map.computeIfAbsent() – hier wird k nicht verwendet:
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
Ab Java 22 (bzw. ab Java 21 mit aktivierten Preview-Features) müssen wir solche Variablen nicht mehr benennen, sondern dürfen (wie in anderen Programmiersprachen lange üblich) stattdessen den Unterstrich (_) verwenden.
Hier das Exception-Beispiel mit unbenannter Variable:
try {
int number = Integer.parseInt(string);
} catch (NumberFormatException _) {
System.err.println("Not a number");
}
Und das computeIfAbsent()-Beispiel:
map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);
Bei einer Exception mag man sich über das Für und Wider einer unbenannten Variable streiten. Wir sind daran gewöhnt, eine Exception mit e zu bezeichnen, bzw. unsere IDE das automatisch machen zu lassen.
Bei computeIfAbsent() hingegen habe ich mir immer Gedanken darüber gemacht, wie ich die nicht benötigte Variable benennen soll. Manchmal wurde es ein k (für „key“), manchmal ein ignored und manchmal ein __ (doppelter Unterstrich). Hier ist die unbenannte Variable eine große Hilfe.
Der Unterstrich hilft nicht nur in catch-Blöcken und Lambdas. Sehen wir uns die weiteren Einsatzorte an.
Die Schleifenvariable in einer for-Schleife
Manchmal interessiert uns an einer for-Schleife nur, wie oft etwas passiert – nicht, womit. Ein Beispiel: Wir wollen zählen, wie viele Elemente ein Iterable enthält. Da Iterable keine size()-Methode hat, müssen wir die Elemente per Schleife durchzählen – und dafür eine Schleifenvariable benennen, die wir gar nicht brauchen:
int count = 0;
for (Order order : orders) {
count++;
}
Die Variable order brauchen wir nicht – also ersetzen wir sie durch den Unterstrich:
int count = 0;
for (Order _ : orders) {
count++;
}
Lokale Variablen, deren Wert wir nicht brauchen
Auch eine ganz normale lokale Variable dürfen wir unbenannt lassen – etwa dann, wenn uns nur der Seiteneffekt eines Methodenaufrufs interessiert. Stell dir vor, wir entnehmen drei Elemente aus einer Queue, benötigen aber nur die ersten beiden:
while (queue.size() >= 3) {
var first = queue.remove();
var second = queue.remove();
var _ = queue.remove(); // entfernt das dritte Element, das wir nicht brauchen
process(first, second);
}
Ein bloßes queue.remove(); ohne Zuweisung ginge auch. Mit var _ bleiben die drei Zeilen aber gleich gebaut, und ein Analysewerkzeug hat keinen ignorierten Rückgabewert zu melden.
Ressourcen in try-with-resources
Ein try-with-resources-Block schließt seine Ressource automatisch – auch dann, wenn wir sie im Block gar nicht direkt ansprechen. Ein typisches Beispiel ist eine eigene Lock-Klasse: Ihre Methode acquire() sperrt das Lock und gibt ein AutoCloseable zurück, dessen close() das Lock wieder freigibt – java.util.concurrent.locks.Lock aus dem JDK ist selbst nicht AutoCloseable.
So sieht der Block mit unbenannter Ressource aus:
try (var _ = lock.acquire()) {
// kritischer Abschnitt – close() gibt das Lock beim Verlassen frei
}
Damit machst du sichtbar: Die Ressource ist wegen ihres Seiteneffekts wichtig (sperren und freigeben), aber im Block greifen wir nicht auf sie zu.
Mehrere Unterstriche nebeneinander
Ein Unterstrich führt keinen Namen ein und verdeckt deshalb auch keine andere Variable. Du darfst ihn im selben Block beliebig oft verwenden. Brauchen wir aus der Queue von oben nur das erste von je drei Elementen, stehen zwei unbenannte Variablen untereinander:
while (queue.size() >= 3) {
var first = queue.remove();
var _ = queue.remove();
var _ = queue.remove();
process(first);
}
Dasselbe gilt für die Parameterliste eines Lambdas. Map.replaceAll() übergibt dem Lambda Key und Wert; wollen wir alle Werte auf 0 setzen, brauchen wir keinen von beiden:
map.replaceAll((_, _) -> 0);
Unbenannte Patterns und Pattern-Variablen
Das Feature heißt nicht „Unnamed Variables“, sondern „Unnamed Variables & Patterns“ und hat somit noch einiges mehr zu bieten – und zwar bei den in Java 21 finalisierten Features Record Patterns und Pattern Matching for Switch.
Die Variable y wird im „then-Block“ des folgenden Code-Beispiels nicht benötigt:
if (object instanceof Position(int x, int y)) {
System.out.println("object is a position, x = " + x);
}
Auch hier können wir deswegen y durch einen Unterstrich ersetzen:
if (object instanceof Position(int x, int _)) {
System.out.println("object is a position, x = " + x);
}
Das nennt sich dann „unbenannte Pattern-Variable“.
Wir können sogar noch einen Schritt weitergehen und das komplette Teil-Pattern int y durch einen Unterstrich ersetzen:
if (object instanceof Position(int x, _)) {
System.out.println("object is a position, x = " + x);
}
Das nennt sich dann „unbenanntes Pattern“.
Im vorigen Beispiel hat das noch keine große Auswirkung; bei verschachtelten Patterns hingegen lässt sich damit eine Menge Platz sparen. Im folgenden Beispiel verwenden wir nur die Variablen x1 und y1, während x2 und y2 unbenutzt sind:
if (object instanceof Path(Position(int x1, int y1), Position(int x2, int y2))) {
System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}
Hier können wir das komplette zweite Position-Pattern durch den Unterstrich ersetzen:
if (object instanceof Path(Position(int x1, int y1), _)) {
System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}
Das stellt doch eine deutliche Verbesserung dar!
Unbenannte Pattern-Variablen und Pattern Matching for Switch
Hier ein Beispiel mit nicht verwendeten Variablen bei Pattern Matching for Switch:
switch (obj) {
case Byte b -> System.out.println("Integer number");
case Short s -> System.out.println("Integer number");
case Integer i -> System.out.println("Integer number");
case Long l -> System.out.println("Integer number");
case Float f -> System.out.println("Floating point number");
case Double d -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
Auch hier dürfen wir alle Variablennamen durch Unterstriche ersetzen:
switch (obj) {
case Byte _ -> System.out.println("Integer number");
case Short _ -> System.out.println("Integer number");
case Integer _ -> System.out.println("Integer number");
case Long _ -> System.out.println("Integer number");
case Float _ -> System.out.println("Floating point number");
case Double _ -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
Wir können sogar noch einen Schritt weitergehen und alle Fälle mit gleichen Aktionen zusammenfassen:
switch (obj) {
case Byte _, Short _, Integer _, Long _ -> System.out.println("Integer number");
case Float _, Double _ -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
Und das ist – neben der prägnanteren Schreibweise – der zweite große Vorteil der unbenannten Pattern-Variable! Mit benannten Variablen wäre das nämlich nicht möglich gewesen. Der folgende Code ist nicht gültig:
switch (obj) {
// Not allowed!
case Byte b, Short s, Integer i, Long l -> System.out.println("Integer number");
case Float f, Double d -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
Dieser Code führt zu folgendem Compilerfehler:
error: illegal fall-through from a pattern
case Byte b, Short s, Integer i, Long l -> System.out.println("Integer number");
^
(the current case label is missing a break)
Für die Float/Double-Zeile meldet der Compiler denselben Fehler. Den Hinweis auf ein fehlendes break hängt der javac-Compiler seit Java 23 an; bei der Pfeil-Schreibweise führt er in die Irre, denn ein break fehlt hier nicht.
Der entscheidende Unterschied ist, dass auf benannte Variablen von nachfolgendem Code aus zugegriffen werden kann, während auf unbenannte Variablen nicht zugegriffen werden darf. Da der Compiler nicht weiß, welches Pattern zur Laufzeit matchen wird, weiß er auch nicht, auf welche der Variablen b, s, i und l zugegriffen werden darf. Daher verbietet er in einem case-Label mit mehreren Patterns benannte Pattern-Variablen ganz – auch eine einzige, wie in case Byte b, Short _. Unbenannte Pattern-Variablen dürfen dagegen in beliebiger Zahl darin stehen.
Mehrere Patterns in einem case-Label gibt es übrigens erst seit diesem Feature. Ein solches Label darf auch einen Guard (when …) haben; der gilt dann für alle Patterns des Labels gemeinsam.
Wo der Unterstrich erlaubt ist und wo nicht
Den Unterstrich gibt es nur für Variablen, die außerhalb ihrer Methode niemand sieht. Als unbenannte Variable darfst du ihn an sechs Stellen deklarieren:
- als lokale Variable,
- als Ressource in try-with-resources,
- im Kopf einer klassischen for-Schleife, z. B.
for (int i = 0, _ = sideEffect(); i < 10; i++), - als Variable einer erweiterten for-Schleife,
- als Exception-Parameter im catch-Block,
- als Lambda-Parameter.
Dazu kommen die unbenannte Pattern-Variable in einem Type Pattern (Integer _) und das unbenannte Pattern _ in einem Record Pattern.
Die folgende Tabelle zeigt, welche Versuche der Compiler abweist und mit welcher Meldung (geprüft mit Java 27):
| Stelle | Beispiel | Fehlermeldung |
|---|---|---|
| Feld | private int _ = 1; | underscore not allowed here |
| Methodenparameter | void log(String _) | underscore not allowed here |
| Lokale Variable ohne Initialisierung | int _; | = expected |
| Lesezugriff | System.out.println(_); | underscore not allowed here |
| Unbenanntes Pattern auf oberster Ebene (statt in einem Record Pattern) | o instanceof _, case _ -> | underscore not allowed here |
Felder und Methodenparameter schließt JEP 456 bewusst aus:
- Ein Feld speichert einen Wert, den andere Methoden des Objekts später lesen – ein Feld ohne Namen könnte keine Methode lesen.
- Für einen unbenannten Methodenparameter wäre offen, was beim Überschreiben gilt und wie sein Javadoc aussieht. Unbenannte Methodenparameter könnten laut JEP Thema eines späteren JEPs werden.
Auf oberster Ebene schreibst du statt _ ein Type Pattern mit unbenannter Pattern-Variable, z. B. o instanceof String _ oder case String _ ->.
Fazit
Unbenannte Variablen und Patterns lösen ein kleines, aber alltägliches Ärgernis: Wir müssen immer wieder Variablen deklarieren, die wir gar nicht verwenden – die Exception im catch-Block, den Key in computeIfAbsent(), die Schleifenvariable beim bloßen Zählen.
Mit dem Unterstrich zeigst du explizit, dass eine Variable bewusst ungenutzt bleibt. Dein Code wird prägnanter und gleichzeitig aussagestärker.
Zudem kannst du mehrere Patterns mit demselben Code in einem case-Label zusammenfassen – das funktioniert nur mit unbenannten Pattern-Variablen, nicht mit benannten.
Felder und Methodenparameter bleiben dagegen benannt: Der Unterstrich ist auf Variablen beschränkt, die außerhalb ihrer Methode niemand sieht.
Unnamed Variables & Patterns werden in JDK Enhancement Proposal 456 definiert.
Hat dir dieser Artikel Zeit gespart? Dann freue ich mich, wenn du eine Minute davon in eine Bewertung auf meinem ProvenExpert-Profil investierst. Dein Feedback zeigt mir, dass sich die Arbeit an diesen Artikeln lohnt.




