在阅读泛型时,我遇到了 PECS( Producer extends
和 Consumer super
有人可以向我解释如何使用 PECS 解决extends
和super
之间的混淆吗?
tl; dr: “PECS” 是从集合的角度来看的。如果您仅从通用集合中提取项目,则它是生产者,应使用extends
;如果您仅将物品塞入其中,则它是消费者,您应该使用super
。如果您都使用同一个集合,则不应同时使用extends
或super
。
假设您有一个方法以事物的集合为参数,但是您希望它比仅接受Collection<Thing>
更为灵活。
情况 1:您想浏览集合并为每个项目做事。
那么列表是生产者,因此您应该使用Collection<? extends Thing>
。
原因是Collection<? extends Thing>
可以包含Thing
任何子类型,因此执行操作时每个元素都将像Thing
(实际上,您不能将任何东西(null 除外)添加到Collection<? extends Thing>
,因为您在运行时无法知道该集合包含Thing
的特定子类型。)
情况 2:您想向集合中添加东西。
那么列表是一个使用者,因此您应该使用Collection<? super Thing>
。
这里的理由是,与Collection<? extends Thing>
, Collection<? super Thing>
无论实际的参数化类型是什么, Collection<? super Thing>
始终可以容纳Thing
在这里,您不必关心列表中已有的内容,只要它可以添加Thing
这是什么? super Thing
保证。
计算机科学背后的原理被称为
? extends MyClass
,? super MyClass
和MyClass
下图应解释该概念。图片提供: Andrey Tyukin
PECS(生产者extends
和消费者super
)
助记符→获取和放置原则。
该原则指出:
extends
super
Java 范例:
class Super {
Number testCoVariance() {
return null;
}
void testContraVariance(Number parameter) {
}
}
class Sub extends Super {
@Override
Integer testCoVariance() {
return null;
} //compiles successfully i.e. return type is don't care(Integer is subtype of Number)
@Override
void testContraVariance(Integer parameter) {
} //doesn't support even though Integer is subtype of Number
}
Liskov 替换原则(LSP)指出 “程序中的对象应该可以用其子类型的实例替换,而不会改变该程序的正确性”。
在编程语言的类型系统中,键入规则
为了说明这种一般现象,请考虑数组类型。对于动物类型,我们可以将动物类型 []
Java 示例:
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
有界(即朝某个地方去)通配符:有 3 种不同的通配符类型:
?
还是? extends Object
-无界通配符。它代表所有类型的家庭。当您得到和放置时使用。? extends T
(它们的亚型的所有类型的家庭T
) - 具有上结合的一个通配符。 T
是在继承层次上- 大多数类。仅当从结构中获取值时,请使用extends
? super T
(所有类型的家庭都是T
超类)- 具有下界的通配符。 T
是继承层次结构中最底层的类。仅将值放入结构时,请使用super
注意:通配符?
表示零或一次,表示未知类型。通配符可以用作参数的类型,而不能用作泛型方法调用,泛型类实例创建的类型参数。(即,当使用通配符时,引用的引用未在程序的其他地方使用,例如我们使用T
)
import java.util.ArrayList;
import java.util.List;
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 {
public static void main(String[] args) {
//? extends Shape i.e. can use any sub type of Shape, here Shape is Upper Bound in inheritance hierarchy
List<? extends Shape> intList5 = new ArrayList<Shape>();
List<? extends Shape> intList6 = new ArrayList<Cricle>();
List<? extends Shape> intList7 = new ArrayList<Rectangle>();
List<? extends Shape> intList9 = new ArrayList<Object>();//ERROR.
//? super Shape i.e. can use any super type of Shape, here Shape is Lower Bound in inheritance hierarchy
List<? super Shape> inList5 = new ArrayList<Shape>();
List<? super Shape> inList6 = new ArrayList<Object>();
List<? super Shape> inList7 = new ArrayList<Circle>(); //ERROR.
//-----------------------------------------------------------
Circle circle = new Circle();
Shape shape = circle; // OK. Circle IS-A Shape
List<Circle> circles = new ArrayList<>();
List<Shape> shapes = circles; // ERROR. List<Circle> is not subtype of List<Shape> even when Circle IS-A Shape
List<? extends Circle> circles2 = new ArrayList<>();
List<? extends Shape> shapes2 = circles2; // OK. List<? extends Circle> is subtype of List<? extends Shape>
//-----------------------------------------------------------
Shape shape2 = new Shape();
Circle circle2= (Circle) shape2; // OK. with type casting
List<Shape> shapes3 = new ArrayList<>();
List<Circle> circles3 = shapes3; //ERROR. List<Circle> is not subtype of List<Shape> even Circle is subetype of Shape
List<? super Shape> shapes4 = new ArrayList<>();
List<? super Circle> circles4 = shapes4; //OK.
}
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Object());//ERROR
list.add(new Shape()); //ERROR
list.add(new Circle()); // ERROR
list.add(new Square()); // ERROR
list.add(new Rectangle()); // ERROR
Shape shape= list.get(0);//OK 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 Object());//ERROR
list.add(new Shape());//OK
list.add(new Circle());//OK
list.add(new Square());//OK
list.add(new Rectangle());//OK
Shape shape= list.get(0); // ERROR. Type mismatch, so list acts only as consumer
Object object= list.get(0); //OK 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.
*/
}
}
协方差和反方差确定基于类型的兼容性。无论哪种情况,方差都是有向关系。协方差可以翻译为 “在同一方向上不同” 或具有不同,而协方差的意思是 “在相反方向上不同” 或相反。协变和逆变类型不同,但是它们之间存在相关性。名称暗示了相关的方向。
https://stackoverflow.com/a/54576828/1697099
https://stackoverflow.com/a/64888058/1697099