Gdy aplikacja ma zareagować inaczej na status zamówienia, rolę użytkownika albo wybraną opcję menu, seria instrukcji if szybko zaczyna przypominać labirynt. Konstrukcja switch w Javie, często opisywana skrótowo jako java switch, porządkuje takie scenariusze i pozwala zapisać je znacznie czytelniej. Pokażę klasyczną składnię, nowoczesne wyrażenia switch, obsługę tekstu i enumów, pattern matching oraz błędy, które najczęściej psują działanie kodu.
Najważniejsze zasady switch w Javie w jednym miejscu
- Switch wybiera jedną z wielu ścieżek na podstawie wartości wyrażenia.
- break zatrzymuje klasyczne przechodzenie do kolejnych przypadków.
- Składnia case ... -> eliminuje przypadkowe przejście między gałęziami.
- Switch expression zwraca wartość i dobrze pasuje do przypisywania wyniku.
- W Javie 21 i nowszej można używać pattern matchingu oraz obsługi
null.

Switch w Javie w praktyce i podstawowa składnia
Instrukcja switch sprawdza wartość jednego wyrażenia, a potem uruchamia kod przypisany do pasującego przypadku. Najlepiej działa wtedy, gdy porównujemy jedną zmienną z kilkoma konkretnymi wartościami, na przykład statusem zamówienia albo nazwą dnia tygodnia.
int punkty = 0;
String poziom = "srebro";
switch (poziom) {
case "brąz":
punkty = 10;
break;
case "srebro":
punkty = 25;
break;
case "złoto":
punkty = 50;
break;
default:
punkty = 0;
}
Każdy blok zaczyna się od etykiety case, a default obsługuje wszystkie wartości, których nie wymieniono wcześniej. W tym przykładzie najważniejsze jest break, ponieważ kończy wykonanie bieżącej gałęzi. Bez niego program przeszedłby do kolejnego case, nawet jeśli nie powinien.
Klasyczna forma nadal jest poprawna i spotyka się ją w starszych projektach. W nowym kodzie najczęściej wybieram jednak zapis z grotami, ponieważ ogranicza liczbę miejsc, w których można popełnić błąd.
Case, break i default czyli skąd bierze się fall-through
Fall-through oznacza automatyczne przejście z jednego przypadku do następnego. Czasem jest zamierzone, na przykład gdy kilka wartości ma wywołać tę samą logikę, ale przypadkowy brak break potrafi stworzyć trudny do zauważenia błąd.
switch (typKonta) {
case "admin":
case "moderator":
System.out.println("Dostęp do panelu");
break;
case "użytkownik":
System.out.println("Dostęp podstawowy");
break;
default:
System.out.println("Brak uprawnień");
}
Połączenie dwóch etykiet bez kodu pomiędzy nimi jest czytelne i bezpieczne. Zarówno admin, jak i moderator trafiają do tego samego fragmentu. Z mojego doświadczenia wynika, że taki zapis jest lepszy niż kopiowanie identycznych instrukcji do kilku gałęzi.
W praktyce default powinien być świadomą decyzją, a nie automatycznym dodatkiem. Przy danych pochodzących z formularza, pliku lub API zwykle warto przewidzieć nieznaną wartość. Przy kompletnym enumie można czasem polegać na pełnym wyliczeniu jego stałych, ale wtedy zmiana modelu danych może wymagać ponownego przejrzenia kodu.
Warto też pamiętać, że switch nie zastępuje każdego warunku. Nie jest dobrym narzędziem do sprawdzania przedziałów, takich jak „wiek od 18 do 25 lat”, ani złożonych zależności logicznych. W takich przypadkach czytelniejsze będzie if, metoda pomocnicza albo osobna reguła biznesowa.
Switch expression zwraca wartość i skraca kod
Od Javy 14 wyrażenie switch może zwracać wartość. To ważna różnica względem klasycznej instrukcji, ponieważ wynik można od razu przypisać do zmiennej, zwrócić z metody albo wykorzystać w innym wyrażeniu.
String komunikat = switch (status) {
case "NOWE" -> "Zamówienie przyjęte";
case "WYSŁANE" -> "Przesyłka jest w drodze";
case "ZREALIZOWANE" -> "Zamówienie zakończone";
default -> "Nieznany status";
};
Składnia case ... -> nie przechodzi automatycznie do kolejnych przypadków. Dzięki temu nie trzeba dodawać break, a intencja kodu jest widoczna od razu. Właśnie ten zapis polecam jako domyślny przy nowych fragmentach aplikacji.
Jeżeli jedna gałąź wymaga kilku instrukcji, można użyć bloku i słowa yield. Służy ono do przekazania wartości z konkretnej gałęzi wyrażenia.
int rabat = switch (poziom) {
case "złoto" -> 20;
case "srebro" -> 10;
case "brąz" -> 5;
default -> {
System.out.println("Brak programu lojalnościowego");
yield 0;
}
};
W tym miejscu początkujący często mylą yield z return. Yield zwraca wartość z wyrażenia switch, ale nie kończy działania całej metody. To drobna różnica, która ma znaczenie przy bardziej rozbudowanych metodach.
String, enum i liczby w instrukcji switch
W codziennym kodzie najczęściej przełączam po wartościach typu int, String oraz po stałych enumów. Każdy wariant ma inne zastosowanie, dlatego typ zmiennej powinien wynikać z modelu danych, a nie z wygody pojedynczego fragmentu.
| Typ | Przykładowe zastosowanie | Na co uważać |
|---|---|---|
int |
kody, poziomy, numery opcji | nieczytelne „magiczne liczby” |
String |
statusy z API lub formularza | literówki i różnice w wielkości liter |
enum |
zamknięty zestaw stanów | zmiana enumu może wymagać uzupełnienia switch |
Enum zwykle wygrywa ze stringiem tam, gdzie lista wartości jest zamknięta. Zamiast porównywać tekst "WYSŁANE", można zdefiniować Status.WYSLANE i otrzymać lepsze wsparcie kompilatora oraz mniejsze ryzyko literówki.
enum Status {
NOWE, WYSŁANE, ZREALIZOWANE
}
String etykieta(Status status) {
return switch (status) {
case NOWE -> "Nowe";
case WYSŁANE -> "Wysłane";
case ZREALIZOWANE -> "Zrealizowane";
};
}
Przy tekście trzeba natomiast pamiętać, że porównanie jest dokładne. Wartość "nowe" nie pasuje do "NOWE", a dodatkowa spacja również zmieni wynik. Przy danych od użytkownika najpierw normalizuję tekst, na przykład przez trim() i ujednolicenie wielkości liter.
Pattern matching pozwala reagować na typ obiektu
W Javie 21 pattern matching dla switch stał się stabilnym elementem języka. Pozwala sprawdzić nie tylko konkretną wartość, ale także typ obiektu i od razu uzyskać zmienną właściwego typu.
String opisz(Object dane) {
return switch (dane) {
case null -> "Brak danych";
case Integer liczba when liczba > 0 -> "Dodatnia liczba";
case Integer liczba -> "Liczba niedodatnia";
case String tekst when tekst.isBlank() -> "Pusty tekst";
case String tekst -> "Tekst o długości " + tekst.length();
default -> "Inny obiekt";
};
}
Zmienna liczba jest dostępna jako Integer, a tekst jako String. Warunek when doprecyzowuje dopasowanie, więc można rozdzielić dodatnie liczby od pozostałych. To rozwiązanie jest czytelniejsze niż seria instanceof i ręczne rzutowania.
Obsługa null wymaga świadomego podejścia. W tradycyjnym switch przekazanie wartości null zwykle kończy się wyjątkiem NullPointerException. W pattern matchingu można jawnie dodać case null, ale nie powinno się używać go jako sposobu na ukrywanie błędów w danych.
Przy projektach działających na starszej wersji Javy trzeba sprawdzić wersję kompilatora i ustawienia środowiska. Kod korzystający z pattern matchingu nie będzie automatycznie działał w projekcie skonfigurowanym na Javę 17, jeśli nie zostanie odpowiednio dostosowany.
Najczęstsze błędy i wybór między switch a if
Najbardziej typowy problem to brak break w starej składni. Drugim jest brak obsługi wartości spoza oczekiwanego zestawu, szczególnie gdy dane przychodzą z zewnętrznego systemu. Trzeci błąd polega na mieszaniu instrukcji i wyrażeń, przez co kod próbuje używać yield albo wartości zwracanej tam, gdzie nie ma takiej potrzeby.
| Sytuacja | Lepszy wybór | Dlaczego |
|---|---|---|
| Kilka konkretnych wartości jednej zmiennej | switch |
kod jest uporządkowany według przypadków |
| Przedziały liczbowe | if |
łatwiej zapisać zakresy i nierówności |
| Złożone warunki logiczne |
if lub metoda reguł |
switch nie powinien ukrywać rozbudowanej logiki |
| Typy obiektów i kilka wariantów danych | pattern matching | typ i warunek można obsłużyć w jednej konstrukcji |
Jeśli używam klasycznego zapisu z dwukropkami, od razu sprawdzam, czy każde przejście ma uzasadnienie. W nowym kodzie preferuję reguły z grotami, bo kompilator i sama struktura programu pomagają uniknąć fall-through.
Nie warto też wrzucać całej logiki biznesowej do jednej instrukcji. Gdy gałęzie zaczynają mieć po kilkadziesiąt linii, lepiej wywołać osobne metody. Dzięki temu switch pozostaje mapą decyzji, a nie miejscem, w którym trzeba szukać całego procesu obsługi zamówienia.
Mały test, który szybko utrwala switch
Najlepsze ćwiczenie polega na napisaniu metody, która dla statusu zamówienia zwraca komunikat, a dla nieznanej wartości sygnalizuje problem. Najpierw warto zrobić wersję z klasycznym break, potem przepisać ją na wyrażenie z grotami i porównać czytelność.
- Dodaj obsługę trzech statusów.
- Połącz dwa statusy w jedną gałąź.
- Sprawdź działanie dla wartości spoza listy.
- Przepisz rozwiązanie na
switch expression. - Dodaj test dla
null, jeśli korzystasz z pattern matchingu.
Moja praktyczna zasada jest prosta. Używaj switch do jasnego wyboru między konkretnymi wariantami, wybieraj zapis z -> w nowym kodzie, a przedziały i złożone warunki zostaw instrukcjom if. Taki podział daje kod łatwiejszy do czytania, testowania i rozwijania wraz z aplikacją.