Przejdź do treści

Switch expressions w Java 13 – nowa składnia switchy

Switch expressions

Switch expressions to kolejna z nowości w Javie 13. Dzięki tej zmianie, kod może wyglądać dużo prościej i czytelniej, a co za tym idzie będzie się pojawiać mniej błędów. Wciąż jest to wersja preview funkcjonalności, która pojawiła się w Javie 12. Jednak po feedbacku ze strony programistów doczekała się kilku zmian. W nowej odsłonie języka zdecydowano się pozostawić ją nadal jako preview tego co nas czeka w kolejnych wydaniach.

Całość zmian opisana jest w JEP 354 (JEP – Java Enhancement Proposal). Wspominałem też o tych zmianach w poprzednim wpisie o wszystkich nowościach w Javie 13. Jednak jest to dość obszerny i ciekawy temat, więc zdecydowałem się poświęcić mu osobny wpis. Pokażę w nim jak używać nowej składni oraz jakie zmiany zaszły w wykorzystaniu starej na różnych przykładach. Dodam też porównanie jak by to wyglądało przed wprowadzeniem zmian.

Przygotowanie

Jak pisałem w poście o blokach tekstu, aby włączyć funkcjonalność w trybie preview trzeba dodać flagę –enable-preview do kompilacji i uruchomienia przykładów, inaczej otrzymamy błąd kompilacji. Jeśli używasz do kompilacji Mavena możesz dodać to w pom.xml:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>13</release>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

Potem przy uruchomieniu podajemy to jako argument:

java --enable-preview -classpath target/classes pl.mloza.switchexpressions.SwitchExpressions

Jeżeli używasz jakiegoś IDE takiego jak IntelliJ to po ustawieniu odpowiedniego JDK powinien sam o to zadbać i dodać automatycznie przy kompilacji.

Pierwszy przykład składni Switch

Zacznijmy od prostego przykładu. Jak wygląda nowa składnia switchy w porównaniu do tego jak można to osiągnąć używając dotychczasowej składni.

public class SwitchExpressions {
public static void main(String[] args) {
DaysOfWeek day = SUNDAY;
System.out.println("Is it a weekend?");
// #1 old way
switch(day) {
case SUNDAY:
case SATURDAY:
System.out.println("Yes!");
break;
case FRIDAY:
System.out.println("Almost!");
break;
case MONDAY:
case TUESDAY:
System.out.println("No");
default:
System.out.println(", but it is closer and closer");
}
// #2 new way
switch (day) {
case SUNDAY, SATURDAY -> System.out.println("Yes!");
case FRIDAY -> System.out.println("Almost!");
case MONDAY, TUESDAY -> System.out.println("No");
default -> System.out.println("No, but it is closer and closer");
}
}
}

Pierwsze co się rzuca w oczy to, że kod jest krótszy, nie mamy tylu linii. To dzięki temu, że możemy do jednego case przypisać kilka wartości, oraz nie musimy wstawiać break po każdym bloku kodu. Jednak przez to, że wykonanie jest przerywane automatycznie, nie możemy wykorzystać tego przy pisaniu programu. W tym przykładzie, przy klasycznym wykonaniu, jeśli day byłby równy MONDAY albo TUESDAY wykonany zostanie kod dla nich oraz dla default, ponieważ nie ma break po bloku kodu dla tych wartości. Nie jest to jednak oczywiste i często prowadzi do błędów jeśli zapomnimy gdzieś przerwać wykonania.

Switch expressions with assignment

Kolejną nowością jaka pojawiła się w 13 przy okazji switch, to możliwość zwracania wartości. Najlepiej zobrazuje to kolejny przykład.

public class SwitchExpressionsTwo {
public static void main(String[] args) {
DaysOfWeek day = SUNDAY;
System.out.println("Is it a weekend?");
// #1 old way
String isWeekend;
switch(day) {
case SUNDAY:
case SATURDAY:
isWeekend = "Yes!";
break;
case FRIDAY:
isWeekend = "Almost!";
break;
case MONDAY:
case TUESDAY:
isWeekend = "No";
break;
default:
isWeekend = "No, but it is closer and closer";
}
System.out.println(isWeekend);
// #2 new way
String isNewWeekend = switch (day) {
case SUNDAY, SATURDAY -> "Yes!";
case FRIDAY -> "Almost!";
case MONDAY, TUESDAY -> "No";
default -> "No, but it is closer and closer";
};
System.out.println(isNewWeekend);
}
}

Jak widzicie, w poprzednich wersjach języka trzeba stworzyć nową zmienną wcześniej i w bloku kodu przypisać jej wartość. Z nową wersją używając strzałki możemy to zrobić bardziej w stylu lambdy gdzie w przypadku jedno-linijkowej funkcji zwracamy wartość od razu. Ale co gdy chcemy wykonać więcej niż jedną operację? Wtedy przychodzi nam z pomocą nowe słowo kluczowe yield.

public class SwitchExpressionsThree {
public static void main(String[] args) {
DaysOfWeek day = SUNDAY;
System.out.println("Is it a weekend?");
String isNewWeekend = switch (day) {
case SUNDAY, SATURDAY -> "Yes!";
case FRIDAY -> "Almost!";
case MONDAY, TUESDAY -> "No";
default -> {
var x = 10 + 13;
// do something
yield "No, but it is closer and closer";
}
};
System.out.println(isNewWeekend);
}
}

Co ciekawe w ten sam sposób możemy też teraz zwrócić wartość używając klasycznej składni.

public class SwitchExpressionsFour {
public static void main(String[] args) {
DaysOfWeek day = SUNDAY;
System.out.println("Is it a weekend?");
String isWeekend = switch(day) {
case SUNDAY:
case SATURDAY:
yield "Yes!";
case FRIDAY:
yield "Almost!";
case MONDAY:
System.out.println("Yw, it's start of the week");
case TUESDAY:
yield "No";
default:
yield "No, but it is closer and closer";
};
System.out.println(isWeekend);
}
}

Jak możecie zauważyć tutaj już nie używamy break, yield przerywa wykonanie i zwraca wartość, ale nadal możemy korzystać z tak zwanego fall through czyli jeśli nie ma na końcu bloku yield/break to przechodzimy do wykonania następnego bloku. Możecie to zauważyć w przypadku MONDAY gdzie jeszcze wypisujemy na konsolę napis „Yw, it’s start of the week”, a następnie w bloku TUESDAY zwracamy „No”.

Switch statements

Możemy przypisywać wartość zwracaną ze switch, ale możemy też użyć ich od razu jako argumenty funkcji. Przykład? Poniżej.

public class SwitchExpressionsFive {
public static void main(String[] args) {
DaysOfWeek day = SUNDAY;
System.out.println("Is it a weekend?");
System.out.println(switch (day) {
case SUNDAY, SATURDAY -> "Yes!";
case FRIDAY -> "Almost!";
case MONDAY, TUESDAY -> "No";
default -> "No, but it is closer and closer";
});
}
}

W ten sam sposób można też użyć starej składni z yield.

Exhaustiveness – wyczerpanie?

Jeśli używamy switchy jako statement (czyli również gdy przypisujemy wynik do zmiennej) to muszą one wyczerpywać wszystkie możliwości. Zwykle załatwia się to dodając default. Jeśli używamy enuma to możemy pokryć wszystkie wartości. Jest to w sumie logiczne, w innym wypadku wartość zwrócona musiałaby być nullem co jest antypatternem.

Podsumowanie

Nowa składnia jest bardziej przyjazna programistom i prowadzi do mniejszej ilości błędów. Poprzednia składnia jest zgodna z językami takimi jak C i C++. Jak się później okazało prowadzi ona do wielu błędów. Niestety decyzje o takim działaniu zostały podjęte na wczesnym etapie projektowania języka i teraz byłyby bardzo trudne do zmiany.

Wprowadzenie nowej składni to również przygotowanie do switchy z użyciem pattern matchingu, dzięki którym możliwe będzie tworzenie takich konstrukcji:

String formatted =
switch (obj) {
case Integer i -> String.format("int %d", i);
case Byte b -> String.format("byte %d", b);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s, s);
default -> String.format("Object %s", obj);
};

Więcej o pattern matchingu możecie przeczytać w JEP 305 oraz na tej stronie: https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

%d