Java Academy · Oppitunti

PECS-periaate

Producer Extends, Consumer Super.

Oppitunti 3/413 vaihetta

PECS-periaate on ilmainen Java Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Java Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Java Academy-kurssilla on yhteensä 4 oppituntia.

Mistä PECS muodostuu

PECS tarkoittaa Producer Extends, Consumer Super. Se on Joshua Blochin Effective Java -kirjasta peräisin oleva muistisääntö.

  • Jos parametri tuottaa T-tyyppisiä arvoja eli luette siitä, käyttäkää ? extends T -rajausta.
  • Jos parametri kuluttaa T-tyyppisiä arvoja eli kirjoitatte siihen, käyttäkää ? super T -rajausta.
public class Main {
    public static void main(String[] args) {
        System.out.println("Producer Extends, Consumer Super");
    }
}

Kanoninen copy-metodi

Klassinen PECS-esimerkki on copy-metodi. Lähde tuottaa arvoja, joten sen tyyppi on ? extends T. Kohde kuluttaa arvoja, joten sen tyyppi on ? 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);
    }
}

Miksi molemmat jokerityypit lisäävät joustavuutta

PECS:n ansiosta copy toimii monilla tyyppiyhdistelmillä: Integer-lähde voidaan kopioida Number- tai Object-kohteeseen ja String-lähde CharSequence-kohteeseen.

Ilman jokerityyppejä tyyppien pitäisi täsmätä täsmälleen, mikä rajoittaisi uudelleenkäyttöä merkittävästi.

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);
    }
}

Esimerkki tuottajasta

Kokoelmastaan vain lukeva metodi käyttää extends-rajausta. Tässä laskemme tuottajan arvot yhteen.

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)));
    }
}

Esimerkki kuluttajasta

Kokoelmaansa vain kirjoittava metodi käyttää super-rajausta. Tässä työnnämme arvoja kuluttajaan.

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);
    }
}

Molemmat roolit: käyttäkää tavallista tyyppiä

Jos parametri toimii sekä tuottajana että kuluttajana, siinä ei pidä käyttää jokerityyppiä.

Metodi, joka lukee ja kirjoittaa samaa listaa, tarvitsee täsmällisen T-tyypin.

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 Comparatorissa

JDK soveltaa PECS-periaatetta laajasti. Collections.max käyttää tyyppiä Comparator<? super T>, koska vertailija kuluttaa T-arvoja vertaillessaan niitä.

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 Stream.collect-metodissa

Monet funktionaaliset rajapinnat noudattavat PECS-periaatetta. Consumer<? super T> voi hyväksyä käsittelijän, joka on kirjoitettu laajempaa tyyppiä varten.

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>
    }
}

Functionin tuottajat ja kuluttajat

Function<? super T, ? extends R> on PECS:n täydellinen muoto: syöte kulutetaan (super) ja tulos tuotetaan (extends).

Tämän ansiosta muunnos voi hyväksyä laajempia syötteitä ja palauttaa suppeampia tuloksia.

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"));
    }
}

Palautustyypit: välttäkää jokerityyppejä

Älkää käyttäkö jokerityyppejä palautustyypeissä. Ne pakottavat myös kutsujat käsittelemään jokerityyppejä ja vuotavat siten monimutkaisuuden rajapinnan käyttäjille.

Palauttakaa konkreettinen tyyppi ja käyttäkää jokerityyppejä parametreissa silloin, kun ne lisäävät joustavuutta.

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)));
    }
}

PECS:n täydellinen yhdistelmä

Kun kaikki yhdistetään: yhdistämisoperaatio lukee kahdesta tuottajasta ja kirjoittaa yhteen kuluttajaan.

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);
    }
}

Pikatarkistus

Soveltakaa PECS-sääntöä.

Kertaus

Opitte PECS-periaatteen:

  • Producer Extends: vain lukuun käytettävissä parametreissa käytetään ? extends T -rajausta.
  • Consumer Super: vain kirjoitukseen käytettävissä parametreissa käytetään ? super T -rajausta.
  • Molemmissa rooleissa käytetään tavallista tyyppiparametria.
  • Välttäkää jokerityyppejä palautustyypeissä.

Seuraavaksi sovellamme näitä ajatuksia todellisten rajapintojen suunnitteluun.

public class Main {
    public static void main(String[] args) {
        System.out.println("PECS recap: Producer Extends, Consumer Super");
    }
}
Aloita maksutta

Opi Java tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
104
Oppitunnit
374

Usein kysytyt kysymykset

Onko oppitunti ”PECS-periaate” ilmainen?

Kyllä – oppitunnin ”PECS-periaate” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Java Academy-kurssin, päivitä CoddyKit PROhon. Java Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”PECS-periaate”?

Producer Extends, Consumer Super. Harjoittelet Java Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Java Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Java Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.

Kuinka kauan ”PECS-periaate”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Java Academy-oppitunnilla?

Kyllä. Jokainen Java Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Ylärajatut jokerityypit
  2. Alarajatut jokerityypit
  3. PECS-periaate
  4. Jokerityypit rajapinnoissa
← Takaisin: Java Academy