0Pricing
Java Academy · Lekcja

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

  1. Symbole wieloznaczne z górnym ograniczeniem
  2. Symbole wieloznaczne z dolnym ograniczeniem
  3. Zasada PECS
  4. Symbole wieloznaczne w API
← Powrót do Java Academy