Java 17: Switch

Aus MimiPedia

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.

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.