Java 17: Switch
Um die Erwartungen vorab zu dämpfen:
Auch Java 17 erlaubt nur die aufzählbaren Daten-Type sowie String, aber keine anderne Objekt-Typen.
Auch führt der Aufruf mit null
weiterhin unweigerlich zu einer Null-Pointer-Exception.
So richtig cool wird das switch
allerdings mit Java 21...
Betrachten wir die klassische Anwendung der switch
-Kontrollstruktur:
public void oldStyleOhneBreak(Animal animal) { switch (animal) { case SPIDER: System.out.println("8 legs"); case ANT, BEE: System.out.println("6 legs"); default: System.out.println("strange animal"); } }
Der Aufruf von oldStyleOhneBreak(Animal.ANT)
liefert:
6 legs strange animal
Wegen des "fall-through"-Verhaltens (was man berechtigterweise mit "Durchfall" übersetzen kann) werden
alle nachfolgenden Fälle durchlaufen, sobald der erste Match auftritt. Um das zu verhindern, muß man nach
jeder case
-Anweisung ein break
einfügen:
public void oldStyleWithBreak(Animal animal) { switch (animal) { case SPIDER: System.out.println("8 legs"); break; case ANT, BEE: System.out.println("6 legs"); break; default: System.out.println("strange animal"); } }
Das ist viel Code, unübersichtlich, fehleranfällig -- und kontrovers.
Java 17 bietet nun eine Alternative, bei der der :
in der Case-Anweisung durch ->
.
Es muß aber ausdrücklich darauf hingeweisen werden, daß es sich hier um keinen λ-Ausdruck handelt.
Das Ergebnis der ersten Fassung sieht nun so aus:
public void newStyle(Animal animal) { switch (animal) { case SPIDER -> System.out.println("8 legs"); case ANT, BEE -> System.out.println("6 legs"); default -> System.out.println("strange animal"); } }
Da für jeden Fall hier nur eine Zeile verwendet wird, ist der gesamte Ausdruck kompakter.
Das könnte man durchaus auch mit dem klassischen case
-Konstrukt hinkriegen.
Überraschender ist aber das Ergebnis, wenn man newStyle(Animal.ANT)
ausführt:
6 legs
Das break
wird nicht mehr benötigt, der neue Stil hat keine Durchfall-Symptome mehr!
Nach dem ->
darf eine beliebige Anweisung kommen.
Anstelle einer einzelnen Anweisung ist also auch ein beliebig großer Code-Block erlaubt.
Da wir in jedem der Fälle die gleiche Aktion ausführen, könnten wir uns auch den Ausgabe-Text liefern lassen
und ihn im Anschluß ausgeben. Genau dafür bietet das neue switch
eine Möglichkeit:
public void newStyleWithReturnValue(Animal animal) { String legs = switch (animal) { case SPIDER -> "8 legs"; case ANT, BEE -> "6 legs"; default -> "strange animal"; }; System.out.println(legs); }
Man kann sich vorstellen, daß switch
immer einen Rückgabe-Typ hat, der allerdings in den vorangehenden Beispielen
void
war und daher nicht gespeichert werden konnte. Java prüft in jedem Fall den Typ und meldet Verstöße die durch
Inkompatibilität entstehen.
Was machen wir nun, wenn nach dem ->
kein Ausdruck vom gewünschten Typ kommt, sondern ein ganzer Code-Block?
Hier hilft das yield
. Es kommt am Ende des Code-Blocks und giebt den Wert zurück:
public void newStyleWithYield(Animal animal) { String legs = switch (animal) { case SPIDER -> "8 legs"; case ANT, BEE -> "6 legs"; case CENTIPEDE -> { System.err.println("lost count"); yield "many legs"; } default -> "strange animal"; }; System.out.println(legs); }
yield
ist -- entgegen der ersten Vermutung -- kein keyword, sondern ein "reservierter Identifier".
So ist die Anweisung
int yield = 5;
durchaus erlaubt. Es erübrigt sich, darauf hinzuweisen diese Verwendung zu vermeiden.
Freundlicherweise darf man das yield
auch mit dem switch
im alten Stil verwenden.
Da dabei das switch
unmittelbar verlassen wird, spart man sich dabei das break
:
public void oldStyleWithYield(Animal animal) { String legs = switch (animal) { case SPIDER: yield "8 legs"; case ANT, BEE: yield "6 legs"; default: yield "strange animal"; }; System.out.println(legs); }
Fazit
Das gemeine switch
wird durch die Verwendung von ->
kompakter und veständlicher.
Folgt man allerdings der Regel möglichst kleine Methoden zu schreiben, wird man praktisch jedes witch
in eine eigene Methode auslagern. Die Rückgabe-Notation bietet dann keine nennenswerten Gewinn mehr,
man spart sich allerdings bei Verwendung von ->
das return
.
Nichstdestotrotz hilft das Feature dabei, legacy-Code zu optimieren und darüber zu refactorn.
Dazu schreibt man das switch
erst auf die Rückgabe-Notation um und lagert sie dann in eine eigene Methode aus.