Symbole wieloznaczne w API
Projektowanie elastycznych metod generycznych
Symbole wieloznaczne w API to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.
Projektowanie elastycznych metod
Wildcardy są przede wszystkim narzędziem dla projektantów interfejsów API. Odpowiednio umieszczony wildcard pozwala wywołującemu przekazywać więcej typów bez rzutowania.
Cel jest następujący: maksymalna elastyczność dla wywołującego przy pełnym bezpieczeństwie typów wewnątrz metody.
import java.util.List;
public class Main {
static double total(List<? extends Number> items) {
double sum = 0;
for (Number n : items) sum += n.doubleValue();
return sum;
}
public static void main(String[] args) {
System.out.println(total(List.of(1, 2, 3)));
System.out.println(total(List.of(1.5, 2.5)));
}
}Wildcardy bez ograniczeń
List<?> to wildcard bez ograniczeń: lista pewnego nieznanego typu.
Należy go używać, gdy metoda nie interesuje się typem elementów, na przykład podczas zliczania elementów lub sprawdzania, czy kolekcja jest pusta.
import java.util.List;
public class Main {
static int size(List<?> any) {
return any.size();
}
public static void main(String[] args) {
System.out.println(size(List.of("a", "b")));
System.out.println(size(List.of(1, 2, 3)));
}
}List<?> a List<Object>
To różne typy. List<Object> przyjmuje tylko listę zadeklarowaną jako Object. List<?> przyjmuje listę dowolnego typu.
Do parametru List<?> można przekazać List<String>, ale nie można jej przekazać do parametru List<Object>.
import java.util.List;
public class Main {
static void printAll(List<?> list) {
for (Object o : list) System.out.println(o);
}
public static void main(String[] args) {
List<String> names = List.of("Ada", "Linus");
printAll(names); // works; List<Object> would reject this
}
}Kiedy parametr typu jest czytelniejszy
Jeśli trzeba odwołać się do typu elementów więcej niż raz, nazwany parametr typu jest czytelniejszy niż wildcard.
Proszę porównać void swap(List<?> l, int i, int j) (wymagające prywatnej metody pomocniczej) z przejrzystą wersją generyczną.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void swap(List<T> list, int i, int j) {
T tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
swap(list, 0, 2);
System.out.println(list);
}
}Sztuczka z przechwytywaniem w metodzie pomocniczej
Jeśli publiczny interfejs API musi używać List<?>, można przekazać obsługę do prywatnej metody pomocniczej z parametrem generycznym, aby przechwycić wildcard.
Metoda pomocnicza nadaje typowi nazwę T, dzięki czemu może ponownie zapisywać dane na liście.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void reverse(List<?> list) { reverseHelper(list); }
private static <T> void reverseHelper(List<T> list) {
for (int i = 0, j = list.size() - 1; i < j; i++, j--) {
T tmp = list.get(i);
list.set(i, list.get(j));
list.set(j, tmp);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4));
reverse(list);
System.out.println(list);
}
}Parametry-producenty w rzeczywistym API
Zaprojektowanie metody addAll, która pobiera dane z dowolnego zgodnego źródła, wymaga górnego ograniczenia źródła.
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class Main {
static <T> void addAll(List<T> target, Collection<? extends T> source) {
for (T t : source) target.add(t);
}
public static void main(String[] args) {
List<Number> nums = new ArrayList<>();
addAll(nums, List.of(1, 2, 3));
System.out.println(nums);
}
}Parametry-konsumenci w rzeczywistym API
Interfejs API miejsca docelowego, który zapisuje wyniki, używa dolnego ograniczenia celu, aby wywołujący mogli przekazywać kontenery szerszych typów.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void repeat(T value, int times, List<? super T> sink) {
for (int i = 0; i < times; i++) sink.add(value);
}
public static void main(String[] args) {
List<Object> out = new ArrayList<>();
repeat("hi", 3, out);
System.out.println(out);
}
}Wildcardy z zagnieżdżonymi typami generycznymi
Zagnieżdżone typy generyczne często wymagają wildcardów. Metoda przyjmująca listę dowolnego rodzaju listy używa typu List<? extends List<?>>.
import java.util.List;
public class Main {
static int countAll(List<? extends List<?>> lists) {
int total = 0;
for (List<?> inner : lists) total += inner.size();
return total;
}
public static void main(String[] args) {
System.out.println(countAll(List.of(List.of(1, 2), List.of("a", "b", "c"))));
}
}Unikanie nadmiernej generalizacji
Nie każda metoda potrzebuje wildcardów. Jeśli wywołujący zawsze przekazują tylko jeden typ, zwykły parametr jest czytelniejszy.
Wildcardy należy dodawać wtedy, gdy rzeczywiste wywołania są blokowane przez niezmienniczość typów, a nie na zapas.
import java.util.List;
public class Main {
// Simple and clear; no wildcard needed for this internal use
static String joinStrings(List<String> parts) {
return String.join(", ", parts);
}
public static void main(String[] args) {
System.out.println(joinStrings(List.of("a", "b", "c")));
}
}Dokumentowanie celu wildcarda
Wildcard sygnalizuje intencję. ? extends T mówi czytelnikom: „Tylko to odczytuję”. ? super T oznacza: „Tylko to zapisuję”.
Wybór właściwego wildcarda jest formą samodokumentującego się kodu, który dodatkowo wymusza przestrzeganie kontraktu.
import java.util.ArrayList;
import java.util.List;
public class Main {
// src: read-only producer; dest: write-only consumer
static <T> void transfer(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Number> dest = new ArrayList<>();
transfer(List.of(1, 2), dest);
System.out.println(dest);
}
}Generyczne API stosu
Słynny przykład stosu autorstwa Blocha: pushAll pobiera dane od producenta (extends), a popAll zapisuje je do konsumenta (super).
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.ArrayList;
import java.util.List;
public class Main {
static class Stack<E> {
private final Deque<E> items = new ArrayDeque<>();
void pushAll(Collection<? extends E> src) { for (E e : src) items.push(e); }
void popAll(Collection<? super E> dst) { while (!items.isEmpty()) dst.add(items.pop()); }
}
public static void main(String[] args) {
Stack<Integer> s = new Stack<>();
s.pushAll(List.of(1, 2, 3));
List<Number> out = new ArrayList<>();
s.popAll(out);
System.out.println(out);
}
}Szybkie sprawdzenie
Proszę sprawdzić swoją intuicję dotyczącą projektowania interfejsów API.
Podsumowanie
Poznali Państwo zasady projektowania interfejsów API z użyciem wildcardów:
List<?>stosuje się w metodach tylko do odczytu, które nie zależą od typu.- Na parametrach należy stosować zasadę PECS, aby zwiększyć elastyczność.
- Gdy trzeba zapisywać za pośrednictwem wildcarda, należy użyć prywatnej metody pomocniczej przechwytującej typ.
- Należy unikać wildcardów w typach zwracanych i nie stosować nadmiernej generalizacji.
Ukończyli Państwo kurs dotyczący wildcardów i PECS.
public class Main {
public static void main(String[] args) {
System.out.println("Wildcards in APIs course complete");
}
}Często zadawane pytania
Czy lekcja „Symbole wieloznaczne w API” jest bezpłatna?
Tak — pełny tekst „Symbole wieloznaczne w API” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Symbole wieloznaczne w API”?
Projektowanie elastycznych metod generycznych Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Java Academy?
Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Symbole wieloznaczne w API”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?
Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Symbole wieloznaczne z górnym ograniczeniem
- Symbole wieloznaczne z dolnym ograniczeniem
- Zasada PECS
- Symbole wieloznaczne w API