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 potrzebneNawiasy 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.
Wyzwanie
ŁatwyZbudujmy 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 polecontenttypuObject. 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 typuTw swojej deklaracji. Potrzebuje prywatnego polacontenttypuT, konstruktora przyjmującego wartość typuToraz metodygetContent(), która zwraca wartość typuT. 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!"
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatności9Typy generyczne
Wprowadzenie do typów generycznychKlasy generyczneMetody generyczneOgraniczone parametry typówSymbole wieloznaczne (?, extends, super)Powtórzenie – kontener generycznyPoćwicz samodzielnie: Kompilator Java online