Zasada PECS
Producer Extends, Consumer Super
Zasada PECS to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 3 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.
Co oznacza PECS
PECS oznacza Producer Extends, Consumer Super. To mnemotechnika pochodząca z książki Effective Java autorstwa Joshuy Blocha.
- Jeśli parametr produkuje wartości typu T, czyli odczytują Państwo z niego dane, należy użyć
? extends T. - Jeśli parametr konsumuje wartości typu T, czyli zapisują Państwo do niego dane, należy użyć
? super T.
public class Main {
public static void main(String[] args) {
System.out.println("Producer Extends, Consumer Super");
}
}Kanoniczna metoda copy
Klasycznym przykładem PECS jest metoda kopiująca. Źródło produkuje, dlatego ma typ ? extends T. Cel konsumuje, dlatego ma typ ? super T.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> src = List.of(1, 2, 3);
List<Object> dest = new ArrayList<>();
copy(src, dest);
System.out.println(dest);
}
}Dlaczego oba wildcardy zwiększają elastyczność
Dzięki PECS metoda copy działa dla wielu kombinacji typów: źródło typu Integer może kopiować dane do celu typu Number lub Object, a źródło typu String do celu typu CharSequence.
Bez wildcardów typy musiałyby być dokładnie takie same, co poważnie ograniczałoby możliwość ponownego użycia kodu.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> ints = List.of(10, 20);
List<Number> nums = new ArrayList<>();
copy(ints, nums);
System.out.println(nums);
}
}Przykład producenta
Metoda, która tylko odczytuje dane ze swojej kolekcji, powinna używać extends. W tym przykładzie sumujemy wartości dostarczane przez producenta.
import java.util.List;
public class Main {
static double sum(List<? extends Number> producer) {
double total = 0;
for (Number n : producer) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.1, 2.2)));
}
}Przykład konsumenta
Metoda, która tylko zapisuje dane w swojej kolekcji, powinna używać super. W tym przykładzie umieszczamy wartości w konsumencie.
import java.util.ArrayList;
import java.util.List;
public class Main {
static void pushDigits(List<? super Integer> consumer) {
for (int i = 0; i < 5; i++) consumer.add(i);
}
public static void main(String[] args) {
List<Number> nums = new ArrayList<>();
pushDigits(nums);
System.out.println(nums);
}
}Obie role: użyj zwykłego typu
Jeśli parametr jest jednocześnie producentem i konsumentem, nie powinien używać wildcarda.
Metoda, która odczytuje i zapisuje dane w tej samej liście, potrzebuje dokładnego typu T.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void rotate(List<T> list) {
if (list.isEmpty()) return;
T last = list.remove(list.size() - 1);
list.add(0, last);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
rotate(list);
System.out.println(list);
}
}PECS w Comparator
JDK stosuje PECS niemal wszędzie. Collections.max przyjmuje Comparator<? super T>, ponieważ komparator konsumuje wartości typu T, aby je porównywać.
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
Comparator<Object> byHash = Comparator.comparingInt(Object::hashCode);
List<String> words = List.of("a", "bb", "ccc");
String max = java.util.Collections.max(words, byHash);
System.out.println(max);
}
}PECS w Stream.collect
Wiele interfejsów funkcyjnych stosuje PECS. Consumer<? super T> może przyjąć procedurę obsługi napisaną dla szerszego typu.
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<Object> printer = o -> System.out.println("got: " + o);
List<String> items = List.of("x", "y");
items.forEach(printer); // forEach takes Consumer<? super T>
}
}Producenci i konsumenci Function
Function<? super T, ? extends R> to pełna postać PECS: dane wejściowe są konsumowane (super), a dane wyjściowe produkowane (extends).
Dzięki temu transformacja może przyjmować dane wejściowe szerszego typu i zwracać dane wyjściowe węższego typu.
import java.util.function.Function;
public class Main {
static <T, R> R apply(Function<? super T, ? extends R> fn, T value) {
return fn.apply(value);
}
public static void main(String[] args) {
Function<Object, Integer> len = o -> o.toString().length();
System.out.println(apply(len, "hello"));
}
}Typy zwracane: unikaj wildcardów
Nie należy używać wildcardów w typach zwracanych. Zmuszają one wywołujący kod do obsługi wildcardów, przenosząc do niego dodatkową złożoność.
Należy zwracać konkretny typ, a wildcardy umieszczać w parametrach, gdy zwiększają elastyczność.
import java.util.ArrayList;
import java.util.List;
public class Main {
// Good: concrete return type
static List<Number> doubled(List<? extends Number> src) {
List<Number> out = new ArrayList<>();
for (Number n : src) out.add(n.doubleValue() * 2);
return out;
}
public static void main(String[] args) {
System.out.println(doubled(List.of(1, 2, 3)));
}
}Pełne zastosowanie PECS podczas scalania
Połączmy wszystko: scalanie odczytuje dane z dwóch producentów i zapisuje je do jednego konsumenta.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void merge(List<? extends T> a, List<? extends T> b, List<? super T> out) {
for (T t : a) out.add(t);
for (T t : b) out.add(t);
}
public static void main(String[] args) {
List<Integer> a = List.of(1, 2);
List<Integer> b = List.of(3, 4);
List<Number> out = new ArrayList<>();
merge(a, b, out);
System.out.println(out);
}
}Szybkie sprawdzenie
Proszę zastosować regułę PECS.
Podsumowanie
Poznali Państwo zasadę PECS:
- Producer Extends: parametry używane tylko do odczytu stosują
? extends T. - Consumer Super: parametry używane tylko do zapisu stosują
? super T. - Parametry pełniące obie role używają zwykłego parametru typu.
- Należy unikać wildcardów w typach zwracanych.
Następnie zastosujemy te idee w projektowaniu rzeczywistych interfejsów API.
public class Main {
public static void main(String[] args) {
System.out.println("PECS recap: Producer Extends, Consumer Super");
}
}Często zadawane pytania
Czy lekcja „Zasada PECS” jest bezpłatna?
Tak — pełny tekst „Zasada PECS” 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 „Zasada PECS”?
Producer Extends, Consumer Super Ć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 3 z 4.
Ile czasu zajmuje lekcja „Zasada PECS”?
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