PECS来自集合的观点。如果你只是从通用集合中得到对象,那么它是生产者,
你应该使用extends; 如果你只是添加对象,它是一个消费者,你应该使用super。
如果同时使用相同的集合,则不应使用extends或super。
假设你有一个方法,它将事物的集合作为参数,但你希望它比仅接受一个更灵活Collection
案例1:你希望浏览集合并对每个项目执行操作。
然后列表是生产者,所以你应该使用Collection<? extends Thing>。
原因是Collection<? extends Thing>可以保存任何子类型Thing,每个Thing类型的对象在执行操作时都这样。
(你实际上无法向a添加任何内容Collection<? extends Thing>,因为你无法在运行时知道该集合的哪个特定子类型Thing。)
案例2:你想要向集合中添加内容。
然后列表是消费者,所以你应该使用Collection<? super Thing>。
这里的推理是不同的Collection<? extends Thing>,无论实际的参数化类型是什么,Collection<? super Thing>都可以随时保持Thing。
在这里你不关心列表中已有的内容,只要它允许Thing添加; 这是什么? super Thing保证。
在一门程序设计语言的类型系统中,一个类型规则或者类型构造器是:
- 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
- 逆变(contravariant),如果它逆转了子类型序关系。
- 不变(invariant),如果上述两种均不适用。
Animal[]并不是总能当作Cat[],因为当一个客户读取数组并期望得到一个Cat,但Animal[]中包含的可能是个Dog。所以逆变规则是不安全,所以如果get一个被斜变修饰的类型返回的是object类型
返回值的协变
在允许协变返回值的语言中, 子类可以重写 getAnimalForAdoption 方法来返回一个更窄的类型:
1 | class CatShelter extends AnimalShelter { |
方法参数的逆变
类似地,子类重写的方法接受更宽的类型也是类型安全(type safe)的:
1 | class CatShelter extends AnimalShelter { |
协变的方法参数类型
在主流的语言中,Eiffel 允许一个重写的方法参数比起父类中的那一个有更加具体的类型,即参数类型协变。因此,Eiffel 版本的 putAnimal 会如下所示:
1 | class CatShelter extends AnimalShelter { |
去除对参数类型协变的依赖
其它语言特性可能用来弥补缺乏参数类型协变的缺乏。
在有泛型(即参数化多态及受限量词)的语言中,前面的例子可用更类型安全的方式重写[5] :不定义 AnimalShelter,改为定义一个参数化的类 Shelter
1 | class Shelter<T extends Animal> { |