Typy generyczne w Javie: var, Optional, sealed
Zaktualizowano 22 października 2026
Typy generyczne (generyki) w Javie znasz być może z ArrayList<String> (zobacz listy i mapy w Javie): lista wie, że trafiają do niej tylko teksty. W tym artykule sam napiszesz takie generyczne klasy i metody. Do tego dochodzą narzędzia z nowszych wersji Javy, które sprawiają, że kod jest krótszy i bezpieczniejszy: var, dopasowywanie wzorców (pattern matching), Optional i sealed.
var i instanceof
Dzięki var pomijasz typ zmiennej lokalnej. Java wyprowadza go z wartości po prawej stronie =: var names = new ArrayList<String>(); ma typ ArrayList<String>. Typ jest potem tak samo ustalony jak zwykle, tylko nie jest zapisany. Przy var typ w nawiasach ostrych piszesz po prawej stronie. var działa też w pętli for-each, ale nie dla pól i parametrów.
Zmienna typu Object może zawierać wszystko. instanceof sprawdza, co jest w środku, i od razu daje ci pasującą zmienną: w if (value instanceof String s) zmienna s jest w bloku if typu String, bez rzutowania.
import java.util.ArrayList;import java.util.List;
public class Main { public static String describe(Object value) { if (value instanceof String s) { return "Text with " + s.length() + " characters"; } else if (value instanceof Integer i) { return "Integer, doubled " + (i * 2); } return "Something else"; }
public static void main(String[] args) { var names = new ArrayList<String>(); names.add("Ada"); names.add("Linus"); for (var name : names) { System.out.println(name.toUpperCase()); } var count = names.size(); System.out.println("Count: " + count);
List<Object> things = List.of("hello", 21, 3.5); for (var thing : things) { System.out.println(describe(thing)); } }}Klasy i metody generyczne
class Box<T> ma parametr typu T. Wewnątrz klasy używasz T jak zwykłego typu. Dopiero przy tworzeniu obiektu ustala się, czym jest T: w Box<String> to String, w Box<Integer> to Integer. Dzięki temu piszesz pudełko raz, a get() i tak zwraca właściwy typ. Rekordy też mogą mieć parametry typu, nawet kilka: record Pair<A, B>(A first, B second).
Także pojedyncza metoda może mieć parametr typu. Stoi on przed typem zwracanym:
public static <T extends Comparable<T>> T smallest(List<T> list)
T extends Comparable<T> oznacza: T musi dać się porównywać. Tylko wtedy możesz w metodzie wywołać compareTo. Metoda działa z liczbami, tekstami i każdą własną klasą, która implementuje Comparable.
import java.util.List;
class Box<T> { private T content;
public void put(T content) { this.content = content; }
public T get() { return content; }}
record Pair<A, B>(A first, B second) {}
public class Main { public static <T extends Comparable<T>> T smallest(List<T> list) { T best = list.get(0); for (T element : list) { if (element.compareTo(best) < 0) { best = element; } } return best; }
public static void main(String[] args) { Box<String> word = new Box<>(); word.put("Java"); String w = word.get(); System.out.println(w.length());
Box<Integer> number = new Box<>(); number.put(7); int n = number.get(); System.out.println(n + 1);
Pair<String, Integer> entry = new Pair<>("Ada", 36); System.out.println(entry);
System.out.println(smallest(List.of(5, 2, 9))); System.out.println(smallest(List.of("pear", "apple", "fig"))); }}Optional
Co ma zwrócić wyszukiwanie, które niczego nie znalazło? O sprawdzeniu null łatwo zapomnieć. Optional<T> pokazuje już w typie, że wartości może nie być. Zawiera albo wartość (Optional.of(x)), albo jest pusty (Optional.empty()).
isPresent()sprawdza, czy wartość istniejeorElse(fallback)zwraca wartość albo wartość zastępcząmap(...)przekształca zawartość, jeśli jakaś jest; pusty zostaje pusty (podobnie jak w strumieniach w Javie)
import java.util.List;import java.util.Optional;
public class Main { public static Optional<String> firstStartingWith(List<String> words, char letter) { for (String w : words) { if (w.charAt(0) == letter) { return Optional.of(w); } } return Optional.empty(); }
public static void main(String[] args) { List<String> fruits = List.of("apple", "banana", "cherry"); Optional<String> b = firstStartingWith(fruits, 'b'); Optional<String> z = firstStartingWith(fruits, 'z');
System.out.println(b.isPresent()); System.out.println(b.orElse("none")); System.out.println(z.orElse("none")); System.out.println(b.map(String::length).orElse(0)); System.out.println(z.map(String::length).orElse(0)); }}sealed i switch ze wzorcami
sealed interface Payment permits Cash, Card, Voucher ustala: istnieją dokładnie te trzy rodzaje Payment i żadne inne. Wtedy switch może decydować bezpośrednio według rodzaju:
case Voucher v ->sprawdza rodzaj i daje ci obiekt jakovcase Cash(double amount) ->od razu rozpakowuje rekord (wzorzec rekordu, record pattern)whendodaje warunek:case Cash(double amount) when amount >= 100 ->
Ponieważ kompilator zna wszystkie dozwolone rodzaje, switch nie potrzebuje default. Jeśli brakuje jakiegoś przypadku, kompilator to zgłosi. Tak jak przy else if, wygrywa pierwszy pasujący przypadek. Dlatego bardziej szczegółowy przypadek z when musi stać pierwszy.
sealed interface Payment permits Cash, Card, Voucher {}
record Cash(double amount) implements Payment {}
record Card(double amount, String bank) implements Payment {}
record Voucher(String code) implements Payment {}
public class Main { public static String describe(Payment p) { return switch (p) { case Cash(double amount) when amount >= 100 -> "Large cash payment: " + amount; case Cash(double amount) -> "Cash: " + amount; case Card(double amount, String bank) -> "Card from " + bank + ": " + amount; case Voucher v -> "Voucher " + v.code(); }; }
public static void main(String[] args) { System.out.println(describe(new Cash(20))); System.out.println(describe(new Cash(150))); System.out.println(describe(new Card(35.5, "Sparkasse"))); System.out.println(describe(new Voucher("SUMMER10"))); }}Typowe błędy
var bez wartości
Kompilator odrzuca
var count;albovar x = null;z komunikatemcannot infer type, bo po prawej stronie nie ma niczego, z czego mógłby odczytać typ.Box<int>
Również we własnych klasach generycznych dozwolone są tylko klasy:
Box<Integer>, a nieBox<int>. Tak samo jest w przypadkuList<Integer>.compareTo bez ograniczenia
Jeśli przed metodą stoi tylko
<T>, Java nic nie wie oT.element.compareTo(best)zgłasza wtedycannot find symbol. Dopiero<T extends Comparable<T>>pozwala na to wywołanie.Ogólny przypadek przed szczegółowym
Jeśli
case Cash c ->stoi przedcase Cash(double amount) when amount >= 100 ->, drugi przypadek nigdy nie zostanie osiągnięty. Kompilator zgłaszathis case label is dominated by a preceding case label.
Zadania z tego tematu
Przećwicz to, czego się nauczyłeś, od razu w przeglądarce. Testy natychmiast sprawdzają twoje rozwiązanie.
Powiązane artykuły
- Listy i mapyListy i mapy w Javie w prosty sposób: ArrayList, HashSet i HashMap z przykładami. Do tego typowe błędy, np. List<int> i remove na liczbach.Czytaj artykuł
- Lambdy i strumienieLambdy i strumienie w Javie w prosty sposób: filter, map, reduce, Comparator i groupingBy z przykładami. Do tego typowe błędy z toList() i reversed().Czytaj artykuł
- Dziedziczenie i interfejsyDziedziczenie w Javie w prosty sposób: extends, super, @Override, klasy abstrakcyjne, interfejsy i Comparable z przykładami. Do tego typowe błędy.Czytaj artykuł