Co to jest Pecs (producent rozszerza Consumer Super)?
Natknąłem się na PECS (skrót od Producent extends
i konsument super
) podczas czytania o generykach.
Może mi ktoś wyjaśnić jak używać PECS do rozwiązywania nieporozumień między extends
i super
?
11 answers
Tl; dr: "PECS" jest z punktu widzenia kolekcji. Jeśli jesteś tylko wyciągając przedmioty z kolekcji generycznej, jest to producent i powinieneś używać extends
; jeśli jesteś tylko wypychając przedmioty, jest to konsument i powinieneś używać super
. Jeśli robisz oba z tą samą kolekcją, nie powinieneś używać extends
ani super
.
Załóżmy, że masz metodę, która przyjmuje jako swój parametr zbiór rzeczy, ale chcesz, aby była bardziej elastyczna niż po prostu akceptuję Collection<Thing>
.
Przypadek 1: chcesz przejść przez kolekcję i robić rzeczy z każdym przedmiotem.
Następnie lista jest producentem , więc powinieneś użyć Collection<? extends Thing>
.
Rozumowanie jest takie, że {[5] } może posiadać dowolny Podtyp Thing
, a zatem każdy element będzie zachowywał się jak Thing
podczas wykonywania operacji. (W rzeczywistości nie można nic dodać do Collection<? extends Thing>
, ponieważ nie można wiedzieć w czasie wykonywania, który specyficzny Podtyp Thing
kolekcji trzyma.)
Przypadek 2: chcesz dodać rzeczy do kolekcji.
Następnie lista jest konsument , więc należy użyć Collection<? super Thing>
.
Rozumowanie tutaj jest takie, że w przeciwieństwie Collection<? extends Thing>
, Collection<? super Thing>
może zawsze posiadać Thing
bez względu na rzeczywisty parametryzowany Typ. Tutaj nie obchodzi cię, co jest już na liście, o ile pozwoli na dodanie Thing
; to gwarantuje ? super Thing
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-29 16:39:36
Zasady stojące za tym w informatyce są nazwane
- Kowariancja - ? extends MyClass,
- Kontrawariancja - ? super MyClass i
- Niezmienniczość / nie-wariancja-MyClass
Poniższy rysunek powinien wyjaśnić tę koncepcję.
Zdjęcie dzięki uprzejmości: Andrey Tyukin
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:41
PECS (skrót od " Producent extends
i konsument super
") Może być wyjaśnione przez : zasada Get and Put
Stwierdza,
- użyj extends wildcard gdy tylko uzyskasz wartości ze struktury
- użyj super wildcard gdy tylko umieść wartości w strukturze
- i nie używaj wieloznaczności gdy zarówno dostać i umieścić.
Zrozummy to na przykładzie:
1. Dla Extends Wildcard(get values czyli Producent extends
)
double
i sumuje je
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums)
s += num.doubleValue();
return s;
}
wywołajmy metodę:
List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;
Od, sum()
zastosowanie metodyextends
, wszystkie poniższe połączenia są legalne.
Pierwsze dwa wywołania nie byłyby legalne, gdyby nie używany.
wyjątek : Możesz Nie umieść niczego w typie zadeklarowanym z extends
wildcard-z wyjątkiem wartości null
, która należy do każdego typu odniesienia:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null); // ok
assert nums.toString().equals("[1, 2, null]");
2. Dla Super Wildcard(wpisz wartości tj. konsument super
)
Oto metoda, która pobiera zbiór liczb i int n
i umieszcza pierwsze n
liczby całkowite, począwszy od zera, w kolekcja:
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
wywołajmy metodę:
List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");
Od, count()
zastosowanie metodysuper
, wszystkie poniższe połączenia są legalne:
Ostatnie dwa połączenia nie byłyby legalne, gdyby nie użyto super.
wyjątek : Możesz nie można uzyskać niczego Z Typu zadeklarowanego przezsuper
wildcard-z wyjątkiem wartości typu Object
, która jest supertyp każdego typu odniesienia:
List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");
3. Gdy zarówno Get and Put, don ' t Use wildcard
Kiedykolwiek zarówno umieścić wartości w i uzyskać wartości z tej samej struktury, nie należy używać wieloznacznych.
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-08 23:28:24
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void testCoVariance(List<? extends B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b); // does not compile
myBlist.add(c); // does not compile
A a = myBlist.get(0);
}
public void testContraVariance(List<? super B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0); // does not compile
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-07 18:15:13
PECS (producent extends
i konsument super
)
Mnemonic → zasada Get and Put.
Zasada ta stwierdza, że:
- używaj wildcard extends, gdy otrzymujesz tylko wartości ze struktury.
- używaj super wildcard, gdy umieszczasz tylko wartości w strukturze.
- i nie używaj symboli wieloznacznych, kiedy dostajesz i wkładasz.
W Javie parameters i generic type parameters nie obsługują dziedziczenia jako / align = "left" /
class Super {
void testCoVariance(Object parameter){} // method Consumes the Object
Object testContraVariance(){ return null;} //method Produces the Object
}
class Sub extends Super {
@Override
void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object
@Override
String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care
}
Zasada substytucji Liskowa: Tablice są kowariantne(niebezpieczne), ale generyczne nie są niezmienne (bezpieczne). tzn. zasada substytucji nie działa z parametryzowanymi typami, co oznacza, że pisanie jest nielegalne.
Kowariantny oznacza po prostu, że jeśli X
jest podtypem Y
, to X[]
będzie również podtypem Y[]
.
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)
List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
Ograniczony (tzn. zmierzający w kierunku gdzieś) wildcard : Istnieją 3 różne smaki wildcards:
- in-variance/Non-variance:
?
lub? extends Object
- Unbounded Wildcard. Oznacza rodzinę WSZYSTKICH rodzajów. Używaj, gdy dostajesz i wkładasz. - Współwariancja:
? extends T
(rodzina WSZYSTKICH rodzajów będących podtypamiT
) - wieloznaczna z górną granicą .T
jest górną - najwyższą klasą w hierarchii dziedziczenia. Użyjextends
wildcard, gdy tylko otrzymasz wartości poza strukturą. - kontra-wariancja:
? super T
(rodzina WSZYSTKICH rodzajów będących supertypamiT
) - wieloznaczna z dolną granicą .T
jest niższą-najwyższą klasą w hierarchii dziedziczenia. Użyjsuper
, gdy tylko umieścisz wartości w strukturze.
Uwaga: symbol wieloznaczny ?
oznacza zero lub jeden raz , reprezentuje Nieznany typ. Symbol wieloznaczny może być użyty jako typ parametru, nigdy nie używany jako typ argument wywołania metody generycznej, utworzenie instancji klasy generycznej.(np. gdy używana jest wieloznaczna nazwa, która nie jest używana w innych programach, takich jak używamy T
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class TestContraVariance {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape shape= list.get(0);//compiles so list act as produces only
/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/
}
/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.
/*You can add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-15 01:08:19
Oznacza to, że gdy parametryzowany Typ przekazywany do metody wytworzy instancje
T
(zostaną z niej w jakiś sposób pobrane), należy użyć? extends T
, ponieważ każda instancja podklasyT
jest równieżT
.Gdy parametryzowany typ jest przekazanie do metody spowoduje, że pochłonie instancje
T
(zostaną przekazane do niej, aby coś zrobić),? super T
powinno być użyte, ponieważ instancjaT
może być legalnie przekazana do dowolnej metody, która akceptuje jakiś supertypT
. AComparator<Number>
może być używany na przykład naCollection<Integer>
.? extends T
nie zadziała, ponieważComparator<Integer>
nie może działać naCollection<Number>
.
Należy pamiętać, że zazwyczaj należy używać tylko ? extends T
i ? super T
dla parametrów jakiejś metody. Metody powinny po prostu używać T
jako parametr typu w ogólnym typie zwrotnym.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-25 12:34:45
W skrócie łatwe do zapamiętania PECS
- Użyj symbolu
<? extends T>
, jeśli chcesz odzyskać obiekt wpiszT
ze zbioru. - Użyj symbolu
<? super T>
, jeśli chcesz umieścić obiekty typuT
w kolekcja. - Jeśli chcesz zadowolić obie rzeczy, cóż, nie używaj żadnych symboli wieloznacznych. Jako to proste.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-13 06:51:24
(Dodawanie odpowiedzi, bo za mało przykładów z generykami)
// Source
List<Integer> intList = Arrays.asList(1,2,3);
List<Double> doubleList = Arrays.asList(2.78,3.14);
List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
for(T n : src){
dest.add(n);
}
}
// Let's try to copy intList to its supertype
copyElements1(intList,numList2); // error, method signature just says "T"
// and here the compiler is given
// two types: Integer and Number,
// so which one shall it be?
// PECS to the rescue!
copyElements2(intList,numList2); // possible
// copy Integer (? extends T) to its supertype (Number is super of Integer)
private static <T> void copyElements2(Collection<? extends T> src,
Collection<? super T> dest) {
for(T n : src){
dest.add(n);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 18:12:22
Zapamiętaj to:
Konsument jeść Kolacja (super); Producent rozszerza jego fabryka rodzica
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-29 06:13:16
Przyjmijmy tę hierarchię:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
Wyjaśnijmy PE-producent rozszerza:
List<? extends Shark> sharks = new ArrayList<>();
Dlaczego nie można dodawać obiektów rozszerzających "Shark" na tej liście? like:
sharks.add(new HammerShark());//will result in compilation error
Ponieważ masz listę, która może być typu A, B lub C w trybie runtime, nie możesz dodać do niej żadnego obiektu typu A, B lub C, ponieważ możesz skończyć z kombinacją, która nie jest dozwolona w Javie.
w praktyce kompilator rzeczywiście widzi w czasie kompilacji, że dodajesz B:
sharks.add(new HammerShark());
...ale nie ma sposobu, aby stwierdzić, czy w czasie wykonywania, Twój B będzie Podtyp lub supertyp typu listy. W czasie wykonywania typem listy może być dowolny z typów A, B, C. Nie można więc na przykład dodać HammerSkark (super type) DO LISTY DeadHammerShark.
*powiesz: "OK, ale dlaczego nie mogę dodać HammerSkark w nim, ponieważ jest to najmniejszy Typ?". Odpowiedź: jest to najmniejszy , który znasz. Kup HammerSkark może być przedłużony też przez kogoś innego i kończy się w ten sam scenariusz.
Wyjaśnijmy CS-Consumer Super:
W tej samej hierarchii możemy spróbować tego:
List<? super Shark> sharks = new ArrayList<>();
Co i dlaczego Możesz dodać do tej listy?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Możesz dodać powyższe typy obiektów, ponieważ wszystko poniżej shark (A,B,C) zawsze będzie podtypem wszystkiego powyżej shark (X, Y, Z). Łatwe do zrozumienia.
Ty nie możesz dodawać typów powyżej rekina, ponieważ w czasie wykonywania typ dodawanego obiektu może być wyższy w hierarchii niż deklarowany typ listy (X, Y,Z). To nie jest dozwolone.
Ale dlaczego nie możesz czytać z tej listy? (Chodzi mi o to, że można wyciągnąć z niego element, ale nie można go przypisać do niczego innego niż obiekt o): {]}
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
W czasie wykonywania, typem listy może być dowolny typ powyżej A: X, Y, Z,... Kompilator może skompilować instrukcję assignment (co wydaje się poprawne), ale w czasie wykonywania typ S (zwierzę) może być niższy w hierarchii niż deklarowany Typ lista (która może być kreatura, lub wyższa). To nie jest dozwolone.
Podsumowując
Używamy <? super T>
do dodawania obiektów typu równego lub poniżej T w liście. nie możemy czytać z
to.
używamy <? extends T>
do odczytywania obiektów typu równego lub poniżej T z listy. nie możemy dodać do niego elementu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-23 17:42:34
Symbole wieloznaczne mogą być używane na trzy sposoby:
- Upper bound Wildcard ( ? extends Type ). - Lower bound Wildcard ( ? super Type ) . - Unbounded Wildcard ( ? ) .
Dla celów tej dyskusji pomocne jest myślenie o zmiennych jako o dostarczeniu jednej z dwóch funkcji:
- In Variable An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest) The src argument provides the data to be copied, so it is the "in" parameter. - Out Variable An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest) the dest argument accepts data, so it is the "out" parameter. An "in" variable is defined with an upper bounded wildcard, using the extends keyword. An "out" variable is defined with a lower bounded wildcard, using the super keyword. In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard. In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard. class NaturalNumber { private int i; public NaturalNumber(int i) { this.i = i; } } class EvenNumber extends NaturalNumber { public EvenNumber(int i) { super(i); } } Consider the following code: List<EvenNumber> le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(35)); // compile-time error You can add null. You can invoke clear. You can get the iterator and invoke remove. You can capture the wildcard and write elements that you've read from the list.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-01 13:12:23