1. Problem klasycznego instanceof
Zacznijmy od odrobiny nostalgii. Przed Java 16, gdy trzeba było sprawdzić typ obiektu i następnie użyć go jako tego typu, trzeba było pisać rozwlekły wzorzec ze sprawdzeniem instanceof i jawnym rzutowaniem:
Object obj = ...; // jakiś obiekt
if (obj instanceof String) {
String s = (String) obj;
System.out.println("Długość łańcucha: " + s.length());
}
Nudny, ale częsty wzorzec
Ten wzorzec pojawia się bardzo często — obsługa zdarzeń, praca z niejednorodnymi kolekcjami, logika biznesowa z dziedziczeniem itd.
Dlaczego to bywa niewygodne
- Duplikacja kodu — trzeba dwukrotnie wskazać typ: i w instanceof, i w rzutowaniu.
- Ryzyko błędu — łatwo przypadkowo zrzutować do niewłaściwego typu i otrzymać ClassCastException.
- „Hałaśliwy” kod — pogarsza czytelność, zwłaszcza przy wielu sprawdzeniach.
2. Pattern Matching for instanceof: nowa składnia
W Java 16 dodano dopasowanie wzorca (pattern matching) dla instanceof. Teraz sprawdzamy typ i od razu deklarujemy zmienną docelowego typu, dostępną wewnątrz gałęzi if.
Nowa składnia
if (obj instanceof String s) {
// s — to już łańcuch!
System.out.println("Długość łańcucha: " + s.length());
}
- Po typie w instanceof podajemy nazwę nowej zmiennej: String s.
- Zmienna jest dostępna tylko wewnątrz bloku, w którym warunek jest prawdziwy.
- Nie trzeba pisać rzutowania (String) — kompilator wszystko gwarantuje.
Na czym polega „magia”? Kompilator sprawdza typ i, jeśli pasuje, „odsłania” zmienną docelowego typu. W przeciwnym razie blok po prostu się nie wykona.
Jak wygląda to w realnym kodzie
Object obj = "Cześć, Java 16+!";
if (obj instanceof String s) {
System.out.println("Wielkie litery: " + s.toUpperCase());
} else {
System.out.println("To nie jest łańcuch!");
}
3. Bezpieczeństwo i czytelność: dlaczego to jest super
Eliminujemy błąd ClassCastException
Object obj = 123;
// String s = (String) obj; // Boom! Wyjątek.
Z pattern matching zmienna tworzy się tylko przy pomyślnym sprawdzeniu typu — „nieprawidłowego” rzutowania po prostu nie ma.
Kod staje się krótszy i bardziej zrozumiały
Dwa wiersze zamieniają się w jeden. Mniej „szumu” — łatwiej czytać i utrzymywać.
Przykład: obsługa różnych typów
public static void printInfo(Object obj) {
if (obj instanceof String s) {
System.out.println("Napis o długości " + s.length());
} else if (obj instanceof Integer i) {
System.out.println("Liczba całkowita: " + (i + 1));
} else {
System.out.println("Nieznany typ: " + obj);
}
}
4. Przykłady użycia i niuanse
Sprawdzanie kilku typów
Object value = ...;
if (value instanceof String s) {
System.out.println("To jest łańcuch: " + s);
} else if (value instanceof Number n) {
System.out.println("To jest liczba: " + n);
} else {
System.out.println("Coś innego: " + value);
}
Użycie z null
Jeśli obiekt jest równy null, to instanceof zawsze zwróci false — NPE nie będzie, ale blok się nie wykona.
Object obj = null;
if (obj instanceof String s) {
// Ten blok NIGDY się nie wykona, jeśli obj == null
System.out.println("Łańcuch: " + s);
} else {
System.out.println("obj — to null albo nie łańcuch");
}
Pattern matching z dziedziczeniem
class Animal {}
class Dog extends Animal {
void bark() { System.out.println("Hau!"); }
}
Animal a = new Dog();
if (a instanceof Dog d) {
d.bark(); // Możemy wywoływać metody Dog bez rzutowania!
}
Przypominamy: kolejność sprawdzeń ma znaczenie — idź od bardziej konkretnego do bardziej ogólnego, inaczej „wąskie” sprawdzenia mogą nie zadziałać.
Porównanie: stare i nowe podejście
| Stary sposób | Nowy sposób (pattern matching) |
|---|---|
|
|
5. Ograniczenia pattern matching dla instanceof
- Zasięg zmiennej. Zmienna z wzorca jest widoczna tylko w tej gałęzi, w której sprawdzenie jest prawdziwe.
if (obj instanceof String s) {
System.out.println(s); // s dostępna
}
// System.out.println(s); // Błąd! s nie jest tu widoczna
- Nie można deklarować kilku zmiennych różnych typów w jednym warunku.
// Błąd kompilacji:
if (obj instanceof String s || obj instanceof Integer i) {
// ...
}
- Wymagana jest Java 16+. Na starszych wersjach JDK nowa składnia nie jest obsługiwana.
6. Praktyczne przykłady (na bazie prostej aplikacji)
Wyobraźmy sobie prosty Task Manager z różnymi typami zadań.
Klasy
class Task {
String title;
public Task(String title) { this.title = title; }
}
class BugTask extends Task {
int severity;
public BugTask(String title, int severity) {
super(title);
this.severity = severity;
}
}
class FeatureTask extends Task {
String feature;
public FeatureTask(String title, String feature) {
super(title);
this.feature = feature;
}
}
Przetwarzanie z pattern matching
public static void processTask(Task t) {
if (t instanceof BugTask bug) {
System.out.println("Bug: " + bug.title + ", krytyczność: " + bug.severity);
} else if (t instanceof FeatureTask feature) {
System.out.println("Funkcja: " + feature.title + ", moduł: " + feature.feature);
} else if (t instanceof Task task) {
System.out.println("Zwykłe zadanie: " + task.title);
}
}
7. Przydatne niuanse
Tabela: pattern matching dla instanceof — plusy i minusy
| Zalety | Wady/Ograniczenia |
|---|---|
| Mniej kodu, lepsza czytelność | Wymaga Java 16+ |
| Brak ryzyka ClassCastException | Zmienna widoczna tylko wewnątrz bloku if |
| Bezpieczeństwo typów na poziomie kompilatora | Nie działa dla wielu typów w jednym warunku |
| Wygodne do obsługi dziedziczenia | Może nie być obsługiwane w starszych IDE i narzędziach budujących |
| Nadaje się do dowolnych klas |
Schemat: jak działa pattern matching dla instanceof
+---------------------------+
| Object obj |
+---------------------------+
|
v
if (obj instanceof Type t)
|
Tak Nie
(true) (false)
| |
v v
t dostępna t nie istnieje
(kod) (brak dostępu)
8. Typowe błędy przy użyciu pattern matching for instanceof
Błąd nr 1: Próba użycia zmiennej poza blokiem if. Zadeklarowałeś zmienną we wzorcu, wyszedłeś z bloku — zmiennej już nie ma. Kompilator słusznie protestuje: „Kim jest s?”
Błąd nr 2: Oczekiwanie, że instanceof zadziała dla null. Jeśli obiekt jest równy null, warunek instanceof jest zawsze fałszywy, zmienna nie jest tworzona. Obsługuj null osobno, jeśli trzeba.
Błąd nr 3: Używanie nowej składni na starej wersji JDK/IDE. W Java 11 i starszych dopasowanie wzorca dla instanceof jest niedostępne — otrzymasz błąd składni. Sprawdź wersję JDK.
Błąd nr 4: Zamieszanie z zasięgiem. Zmienna z wzorca jest dostępna tylko w tej gałęzi, w której sprawdzenie jest prawdziwe. Poza nią — nie istnieje.
Błąd nr 5: Oczekiwanie wsparcia wielu typów jednocześnie. Nie można zadeklarować od razu dwóch zmiennych różnych typów w jednym warunku: if (obj instanceof String s || obj instanceof Integer i) — tak nie wolno. Dla każdego typu — osobne sprawdzenie.
GO TO FULL VERSION