Menu
Coddy logo textTech

Wprowadzenie do typów generycznych

Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 53 z 87.

Wyobraź sobie, że musisz utworzyć klasę, która przechowuje wartość i później ją zwraca. Bez typów generycznych musiałbyś napisać osobne klasy dla każdego typu: jedną dla Integer, jedną dla String, jedną dla Double i tak dalej. Prowadzi to do duplikacji kodu i problemów z jego utrzymaniem.

Typy generyczne rozwiązują ten problem, umożliwiając pisanie klas i metod działających z dowolnym typem, przy jednoczesnym zachowaniu bezpieczeństwa typów. Definiujesz symbol zastępczy (nazywany parametrem typu), który zostaje zastąpiony rzeczywistym typem, gdy używasz klasy:

// Bez typów generycznych – używa Object, wymaga rzutowania
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0);  // Wymagane ręczne rzutowanie

// Z typami generycznymi – bezpieczeństwo typów, bez rzutowania
List<String> list = new ArrayList<String>();
list.add("Hello");
String s = list.get(0);  // Rzutowanie nie jest potrzebne

Nawiasy ostrokątne <T> oznaczają parametr typu. Gdy zapisujesz List<String>, informujesz kompilator, że ta lista będzie zawierać wyłącznie ciągi znaków. Jeśli spróbujesz dodać liczbę całkowitą, kompilator od razu wykryje błąd, zamiast dopiero podczas działania programu.

Typy generyczne mają dwie kluczowe zalety: eliminują potrzebę jawnego rzutowania i wykrywają błędy typów podczas kompilacji, zamiast w czasie wykonywania. Dzięki temu Twój kod jest bezpieczniejszy i bardziej przejrzysty. W kolejnych lekcjach przyjrzymy się, jak tworzyć własne klasy i metody generyczne.

challenge icon

Wyzwanie

Łatwy

Zbudujmy prosty system przechowywania, który pokaże możliwości typów generycznych, porównując stare podejście (użycie Object z rzutowaniem) z nowym (użycie typów generycznych zapewniających bezpieczeństwo typów).

Utworzysz trzy pliki, aby na własne oczy zobaczyć różnicę:

  • OldBox.java: Utwórz klasę, która przechowuje wartości w sposób stosowany przed wprowadzeniem typów generycznych. Twoja klasa OldBox powinna mieć prywatne pole content typu Object. Dodaj konstruktor, który przyjmuje obiekt i go przechowuje, oraz metodę getContent(), która zwraca obiekt. To podejście działa, ale podczas pobierania wartości wymaga rzutowania.
  • GenericBox.java: Teraz utwórz nowoczesną wersję zapewniającą bezpieczeństwo typów, używając typów generycznych. Twoja klasa GenericBox powinna używać parametru typu T w swojej deklaracji. Potrzebuje prywatnego pola content typu T, konstruktora przyjmującego wartość typu T oraz metody getContent(), która zwraca wartość typu T. Korzystając z tej klasy, nie trzeba rzutować!
  • Main.java: Pokaż oba podejścia obok siebie. Otrzymasz dwa dane wejściowe: słowo (String) i liczbę (integer).

    Najpierw użyj starego podejścia: utwórz OldBox przechowujący słowo, a następnie pobierz je, rzutując na String, i wypisz: OldBox (with cast): [value]

    Następnie użyj podejścia generycznego: utwórz GenericBox<String> przechowujący to samo słowo, pobierz je bez rzutowania i wypisz: GenericBox (no cast): [value]

    Potem pokaż działanie typów generycznych z liczbami całkowitymi: utwórz GenericBox<Integer> przechowujący liczbę, pobierz ją i wypisz: GenericBox Integer: [value]

    Na koniec wypisz pustą linię, a następnie: Type safety: Generics catch errors at compile time!

Otrzymasz dwa dane wejściowe w następującej kolejności: słowo (String) i liczbę (integer jako String, którą sparsujesz).

Zwróć uwagę na kluczową różnicę: w przypadku OldBox musisz rzutować za pomocą (String) box.getContent() i mieć nadzieję, że typ jest prawidłowy. W przypadku GenericBox kompilator zna typ i rzutowanie nie jest potrzebne. To podstawowa zaleta typów generycznych: bezpieczeństwo typów bez kłopotliwego rzutowania!

Spróbuj swoich sił

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String word = scanner.nextLine();
        int number = Integer.parseInt(scanner.nextLine());
        
        // TODO: Utwórz OldBox przechowujący słowo
        // Pobierz, rzutując na String, i wypisz: "OldBox (with cast): [value]"
        
        // TODO: Utwórz GenericBox<String> przechowujący słowo
        // Pobierz bez rzutowania i wypisz: "GenericBox (no cast): [value]"
        
        // TODO: Utwórz GenericBox<Integer> przechowujący liczbę
        // Pobierz i wypisz: "GenericBox Integer: [value]"
        
        // TODO: Wypisz pusty wiersz, a następnie wypisz:
        // "Type safety: Generics catch errors at compile time!"
    }
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Java online