问题
I came across PECS (short for Producer extends
and Consumer super
) while reading up on generics.
Can someone explain to me how to use PECS to resolve confusion between extends
and super
?
回答1:
tl;dr: "PECS" is from the collection's point of view. If you are only pulling items from a generic collection, it is a producer and you should use extends
; if you are only stuffing items in, it is a consumer and you should use super
. If you do both with the same collection, you shouldn't use either extends
or super
.
Suppose you have a method that takes as its parameter a collection of things, but you want it to be more flexible than just accepting a Collection<Thing>
.
Case 1: You want to go through the collection and do things with each item.
Then the list is a producer, so you should use a Collection<? extends Thing>
.
The reasoning is that a Collection<? extends Thing>
could hold any subtype of Thing
, and thus each element will behave as a Thing
when you perform your operation. (You actually cannot add anything to a Collection<? extends Thing>
, because you cannot know at runtime which specific subtype of Thing
the collection holds.)
Case 2: You want to add things to the collection.
Then the list is a consumer, so you should use a Collection<? super Thing>
.
The reasoning here is that unlike Collection<? extends Thing>
, Collection<? super Thing>
can always hold a Thing
no matter what the actual parameterized type is. Here you don't care what is already in the list as long as it will allow a Thing
to be added; this is what ? super Thing
guarantees.
回答2:
The principles behind this in computer science is called
- Covariance:
? extends MyClass
, - Contravariance:
? super MyClass
and - Invariance/non-variance:
MyClass
The picture below should explain the concept. Picture courtesy: Andrey Tyukin
回答3:
PECS (Producer extends
and Consumer super
)
mnemonic → Get and Put principle.
This principle states that:
- Use an extends wildcard when you only get values out of a structure.
- Use a super wildcard when you only put values into a structure.
- And don’t use a wildcard when you both get and put.
Example in Java:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
Liskov substitution principle: if S is a subtype of T, then objects of type T may be replaced with objects of type S.
Within the type system of a programming language, a typing rule
- covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic;
- contravariant if it reverses this ordering;
- invariant or nonvariant if neither of these applies.
Covariance and contravariance
- Read-only data types (sources) can be covariant;
- write-only data types (sinks) can be contravariant.
- Mutable data types which act as both sources and sinks should be invariant.
To illustrate this general phenomenon, consider the array type. For the type Animal we can make the type Animal[]
- covariant: a Cat[] is an Animal[];
- contravariant: an Animal[] is a Cat[];
- invariant: an Animal[] is not a Cat[] and a Cat[] is not an Animal[].
Java Examples:
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
more examples
bounded(i.e. heading toward somewhere) wildcard : There are 3 different flavours of wildcards:
- In-variance/Non-variance:
?
or? extends Object
- Unbounded Wildcard. It stands for the family of all types. Use when you both get and put. - Co-variance:
? extends T
(the family of all types that are subtypes ofT
) - a wildcard with an upper bound.T
is the upper-most class in the inheritance hierarchy. Use anextends
wildcard when you only Get values out of a structure. - Contra-variance:
? super T
( the family of all types that are supertypes ofT
) - a wildcard with a lower bound.T
is the lower-most class in the inheritance hierarchy. Use asuper
wildcard when you only Put values into a structure.
Note: wildcard ?
means zero or one time, represents an unknown type. The wildcard can be used as the type of a parameter, never used as a type argument for a generic method invocation, a generic class instance creation.(i.e. when used wildcard that reference not used in elsewhere in program like we use 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 Test {
/*
* 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<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
generics and examples
回答4:
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
}
}
回答5:
As I explain in my answer to another question, PECS is a mnemonic device created by Josh Bloch to help remember Producer extends
, Consumer super
.
This means that when a parameterized type being passed to a method will produce instances of
T
(they will be retrieved from it in some way),? extends T
should be used, since any instance of a subclass ofT
is also aT
.When a parameterized type being passed to a method will consume instances of
T
(they will be passed to it to do something),? super T
should be used because an instance ofT
can legally be passed to any method that accepts some supertype ofT
. AComparator<Number>
could be used on aCollection<Integer>
, for example.? extends T
would not work, because aComparator<Integer>
could not operate on aCollection<Number>
.
Note that generally you should only be using ? extends T
and ? super T
for the parameters of some method. Methods should just use T
as the type parameter on a generic return type.
回答6:
In a nutshell, three easy rules to remember PECS:
- Use the
<? extends T>
wildcard if you need to retrieve object of typeT
from a collection. - Use the
<? super T>
wildcard if you need to put objects of typeT
in a collection. - If you need to satisfy both things, well, don’t use any wildcard. As simple as that.
回答7:
(adding an answer because never enough examples with Generics wildcards)
// 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);
}
}
回答8:
let's assume this hierarchy:
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
Let's clarify PE - Producer Extends:
List<? extends Shark> sharks = new ArrayList<>();
Why you cannot add objects that extend "Shark" in this list? like:
sharks.add(new HammerShark());//will result in compilation error
Since you have a list that can be of type A, B or C at runtime, you cannot add any object of type A, B or C in it because you can end up with a combination that is not allowed in java.
In practice, the compiler can indeed see at compiletime that you add a B:
sharks.add(new HammerShark());
...but it has no way to tell if at runtime, your B will be a subtype or supertype of the list type. At runtime the list type can be any of the types A, B, C. So you cannot end up adding HammerSkark (super type) in a list of DeadHammerShark for example.
*You will say: "OK, but why can't I add HammerSkark in it since it is the smallest type?". Answer: It is the smallest you know. But HammerSkark can be extended too by somebody else and you end up in the same scenario.
Let's clarify CS - Consumer Super:
In the same hierarchy we can try this:
List<? super Shark> sharks = new ArrayList<>();
What and why you can add to this list?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
You can add the above types of objects because anything below shark(A,B,C) will always be subtypes of anything above shark (X,Y,Z). Easy to understand.
You cannot add types above Shark, because at runtime the type of added object can be higher in hierarchy than the declared type of the list(X,Y,Z). This is not allowed.
But why you cannot read from this list? (I mean you can get an element out of it, but you cannot assign it to anything other than Object o):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
At runtime, the type of list can be any type above A: X, Y, Z, ... The compiler can compile your assignment statement (which seems correct) but, at runtime the type of s (Animal) can be lower in hierarchy than the declared type of the list(which could be Creature, or higher). This is not allowed.
To sum up
We use <? super T>
to add objects of types equal or below T in list. We cannot read from
it.
We use <? extends T>
to read objects of types equal or below T from list. We cannot add element to it.
回答9:
Remember this:
Consumer eat supper(super); Producer extends his parent's factory
回答10:
Using real life example (with some simplifications):
- Imagine a freight train with freight cars as analogy to a list.
- You can put a cargo in a freight car if the cargo has the same or smaller size than the freight car =
<? super FreightCarSize>
- You can unload a cargo from a freight car if you have enough place (more than the size of the cargo) in your depot =
<? extends DepotSize>
回答11:
Covariance: accept subtypes
Contravariance: accept supertypes
Covariant types are read-only, while contravariant types are write-only.
来源:https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super