CodeGym /Kursy /JAVA 25 SELF /Generics: po co są potrzebne, podstawowa składnia

Generics: po co są potrzebne, podstawowa składnia

JAVA 25 SELF
Poziom 26 , Lekcja 4
Dostępny

1. Problem „surowych” kolekcji (raw types)

Na chwilę zanurzymy się w historię. Do czasu Java 5 wszystkie kolekcje były „wszystkożerne”. Przechowywały obiekty typu Object, a kompilator nie kontrolował, co dokładnie do nich wkładasz. Chcesz włożyć łańcuch? Proszę bardzo. Liczbę? Czemu nie. Kota? Też można.

// Przykład "surowych" kolekcji (raw types), Java przed wersją 5
List list = new ArrayList();
list.add("Cześć");
list.add(42);
list.add(new Object());

Problem ujawniał się przy wyjmowaniu i używaniu wartości:

String s = (String) list.get(0); // OK, to jest String
String s2 = (String) list.get(1); // BUM! ClassCastException

Kompilator milczy, a w czasie wykonania dostajesz ClassCastException. To jak pudełko z napisem „jabłka”, w którym leżą kubek, banan i jeżyk.

Dlaczego to jest złe?

  • Błędy ujawniają się dopiero w czasie wykonywania.
  • Mylą się typy: trzeba ręcznie rzutować obiekty na właściwy typ (cast).
  • Kod jest mniej czytelny i bardziej ryzykowny.

Rozwiązanie — generics (typy generyczne)

Generics (typy generyczne) to mechanizm pozwalający tworzyć klasy, interfejsy i metody z parametrami typu. Mówisz kolekcji: „Przechowuj tylko Stringi”, a kompilator tego pilnuje.

List<String> words = new ArrayList<>();
words.add("Cześć");
words.add("Świat");
// words.add(42); // Błąd kompilacji! Nie można dodać int do List<String>

Teraz kompilator nie pozwoli włożyć do listy niczego poza Stringami. Błąd zostanie wychwycony przed uruchomieniem programu.

Główna idea generics:
Zapewnić bezpieczeństwo typów kolekcji (i nie tylko), aby błędy były wychwytywane na etapie kompilacji, a nie w czasie wykonywania.

2. Składnia generics: jak to wygląda w kodzie

Wskazywanie typu w nawiasach kątowych

Tworząc kolekcję, wskaż typ elementów w <>:

List<String> names = new ArrayList<>();
names.add("Alicja");
names.add("Bob");
// names.add(123); // Błąd: nie można dodać liczby do listy Stringów

String first = names.get(0); // Nie trzeba rzutowania!

Klasyka:

  • List<String> — lista Stringów
  • List<Integer> — lista liczb całkowitych
  • Set<Double> — zbiór liczb zmiennoprzecinkowych
  • Map<String, Integer> — klucz String, wartość Integer

Dlaczego nie pisać po prostu List?

Można, ale tracisz wszystkie zalety generics i kompilator ostrzeże:

List list = new ArrayList(); // raw type — niewskazane!
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Witaj, ClassCastException!

Współczesny kod Java zawsze używa generics.

Operator diamentu <>

Od Java 7 nie trzeba podawać typu po prawej stronie, jeśli wynika on z kontekstu:

List<String> list = new ArrayList<>(); // Kompilator sam zrozumie, że chodzi o <String>

3. Generics dla różnych kolekcji

Przykłady dla List, Set, Map

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alicja");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alicja", 23);
ages.put("Bob", 31);

Przykład z własną klasą

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. Przydatne niuanse

Zalety generics

Bezpieczeństwo typów. Kompilator pilnuje, by do kolekcji trafiały tylko elementy wymaganego typu.

Brak potrzeby rzutowania typów. Kiedyś: String s = (String) list.get(0);. Teraz: String s = list.get(0);.

Kod czytelniejszy i bardziej niezawodny. Mniej niespodzianek w czasie wykonania.

Ograniczenia generics

Nie można używać typów prymitywnych. Generics działają tylko z obiektami, nie z prymitywami (int, double, boolean). Używaj klas opakowujących: Integer, Double, Boolean.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int automatycznie zamienia się w Integer (autoboxing)

Krótko o wymazywaniu typów (type erasure)

W Javie generics są zaimplementowane poprzez mechanizm wymazywania typów: po kompilacji informacja o parametrach typu jest wymazywana i w czasie wykonania JVM nie wie, że to był List<String>, a nie po prostu List. Zrobiono to ze względu na wsteczną kompatybilność.

Konsekwencja: nie można sprawdzić parametru typu przez instanceof z konkretnym argumentem typu.

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Błąd kompilacji!

Próba dodania elementu innego typu — błąd kompilacji

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Błąd kompilacji: incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Kot", 5);
// map.put(3, "Słoń"); // Błąd: klucz musi być String, a wartość — Integer

I to świetnie: błędy są wychwytywane na etapie kompilacji.

Nie tylko kolekcje

Generics można stosować we własnych klasach i metodach. Na przykład uniwersalne „Pudełko”:

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Cześć");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

W kolekcjach generics to standard, ale spotkasz je też w innych miejscach, np. w Stream API i Optional.

5. Typowe błędy przy pracy z generics

Błąd nr 1: używanie „surowych” kolekcji. Zapis w stylu List list = new ArrayList(); pozbawia bezpieczeństwa typów. Zawsze podawaj parametry typu, np. List<String>.

Błąd nr 2: próba użycia typów prymitywnych. Nie można napisać List<int>, użyj List<Integer>.

Błąd nr 3: ręczne rzutowanie przy odczycie z kolekcji. Jeśli używasz generics, rzutowanie w stylu (String) list.get(i) nie jest potrzebne. Jeśli musisz — gdzieś naruszyłeś deklarowane typy.

Błąd nr 4: oczekiwanie, że parametry typów są dostępne w czasie wykonania. Z powodu wymazywania typów nie można sprawdzać ich przez instanceof w rodzaju List<String>.

Błąd nr 5: mieszanie różnych typów w jednej kolekcji. Jeśli zadeklarowano List<String>, nie próbuj dodawać Integer — kompilator na to nie pozwoli, i dobrze.

1
Ankieta/quiz
Kolekcje i generics, poziom 26, lekcja 4
Niedostępny
Kolekcje i generics
Kolekcje i generics
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION