Symbole wieloznaczne (?, extends, super)
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 57 z 87.
Parametry typów z ograniczeniem zawężają typy generyczne do określonych typów, ale co w sytuacji, gdy pracujesz z istniejącymi typami generycznymi i nie znasz dokładnego parametru typu lub nie ma to dla ciebie znaczenia? Właśnie wtedy przydają się typy wieloznaczne.
Nieograniczony symbol wieloznaczny ? reprezentuje nieznany typ. Jest przydatny, gdy chcesz zaakceptować dowolny typ sparametryzowany:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Działa
printList(new ArrayList<Integer>()); // DziałaTyp wieloznaczny z górnym ograniczeniem ? extends Type akceptuje określony typ lub dowolny z jego podtypów. Używaj go, gdy chcesz odczytywać dane z generycznej struktury:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // Lista obiektów Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // Lista obiektów DoubleWieloznacznik z dolnym ograniczeniem ? super Type akceptuje określony typ lub dowolny z jego nadtypów. Używaj go, gdy musisz zapisywać do struktury generycznej:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Działa - Number jest nadtypem IntegerPomocna wskazówka: używaj extends, gdy tylko odczytujesz (producent), i używaj super, gdy tylko zapisujesz (konsument). Jest to znane jako zasada PECS: producent używa extends, konsument — super.
Wyzwanie
ŁatwyStwórzmy system karmienia w zoo, który pokazuje wszystkie trzy typy symboli wieloznacznych! Utworzysz hierarchię zwierząt i pożywienia, a następnie napiszesz metody pomocnicze, które używają symboli wieloznacznych bez ograniczeń, z górnym ograniczeniem i z dolnym ograniczeniem do obsługi różnych scenariuszy karmienia.
Rozmieścisz kod w czterech plikach:
Food.java: Utwórz prostą hierarchię klas dla pożywienia. Zacznij od klasy bazowejFood, która ma prywatne polename(String), konstruktor do jego inicjalizacji oraz metodęgetName(). Następnie utwórz w tym samym pliku dwie podklasy:Meatrozszerza Food (konstruktor wywołujesuper(name)) orazVegetablerozszerza Food (ten sam schemat).Animal.java: Utwórz klasę bazowąAnimalz prywatnym polemspecies(String), konstruktorem i metodągetSpecies(). Utwórz dwie podklasy:CarnivoreiHerbivore, z których każda rozszerza Animal i wywołuje konstruktor klasy nadrzędnej.ZooFeeder.java: To tutaj symbole wieloznaczne pokazują swoje możliwości! Utwórz klasę pomocniczą z trzema metodami statycznymi:printInventory(List<?> items)- Używa symbolu wieloznacznego bez ograniczeń, aby wypisać dowolną listę. Przejdź przez nią i wypisz każdy element za pomocątoString(), po jednym w każdym wierszu.calculateTotalFood(List<? extends Food> foods)- Używa symbolu wieloznacznego z górnym ograniczeniem, aby odczytywać listę dowolnego typu Food. Zwróć liczbę elementów jakoint. To demonstruje odczyt z producenta.addMeatToStock(List<? super Meat> stock, String meatName)- Używa symbolu wieloznacznego z dolnym ograniczeniem, aby zapisywać do listy, która może przechowywać Meat. Utwórz nowy obiektMeato podanej nazwie i dodaj go do zapasów. To demonstruje zapis do konsumenta.Main.java: Połącz wszystkie elementy swojego systemu karmienia w zoo! Otrzymasz dwa dane wejściowe: nazwę mięsa (String) i nazwę warzywa (String).Najpierw utwórz
ArrayList<Food>i dodaj obiektMeato nazwie mięsa oraz obiektVegetableo nazwie warzywa. WypiszFood inventory:, a następnie wywołajprintInventory, przekazując tę listę.Następnie wypisz pusty wiersz, a potem wypisz
Total food items: [count], używająccalculateTotalFoodna liście pożywienia.Na koniec utwórz nowy
ArrayList<Food>o nazwiemeatStock. Wypisz pusty wiersz, a następnieAdding to meat stock.... WywołajaddMeatToStock, przekazując tę listę i ciąg znaków"Beef". Następnie wypiszMeat stock after adding:i wywołajprintInventorydla zapasów mięsa.
Otrzymasz dwa dane wejściowe w podanej kolejności: nazwę mięsa i nazwę warzywa.
Nadpisz toString() w klasie Food, aby zwracała nazwę pożywienia, dzięki czemu wypisywanie będzie działać poprawnie. Zwróć uwagę, jak każdy typ symbolu wieloznacznego służy innemu celowi: ? zapewnia maksymalną elastyczność, gdy wystarczy odczytać elementy jako Object, ? extends służy do odczytu określonych typów, a ? super do zapisu do kolekcji!
Spróbuj swoich sił
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: Utwórz ArrayList<Food> i dodaj obiekty Meat oraz Vegetable
// TODO: Wypisz "Food inventory:", a następnie wywołaj printInventory
// TODO: Wypisz pusty wiersz, a następnie "Total food items: [count]", używając calculateTotalFood
// TODO: Utwórz nowy ArrayList<Food> o nazwie meatStock
// TODO: Wypisz pusty wiersz, a następnie "Adding to meat stock..."
// TODO: Wywołaj addMeatToStock z argumentami meatStock i "Beef"
// TODO: Wypisz "Meat stock after adding:", a następnie wywołaj printInventory dla meatStock
}
}
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