这里写自定义目录标题
PECS 原则
自从原来的 csdn 泄密事件后,csdn被清库后,就一直没再写优快云了,今天就算是中秋开个章。
PECS 全称 Producter Extends, Consumer Super 原则,即
供应者使用 Extends,消费者使用 Super。这个原则其实质来源于 Liskov替换原则(LSP) ,即 所有出现基类(父类)的地方都可以用子类进行替换。
先回顾下LSP,LSP包含四层含义:
- 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;
- 子类中可以增加自己的方法;
- 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更加宽松;
- 当子类覆盖或实现父类的方法时,方法的返回值要比父类方法的更加严格。
针对LSP四层含义的③④条,就引出了协变(Covariance)和逆变(Contravariance)的概念:
- 协变,简言之,就是父类型到子类型,变得越来越具体,在Java中体现在返回值类型不变或更加具体(异常类型也是如此)等。
- 逆变,简言之,就是父类型到子类型,变得越来越具体,但是方法的形参却变得更加抽象或不变。
可能各位看官看了更迷糊了,下一章节再用例子讲下。
描述
分享一个来自stackoverflow的图片:
来源: 点击
上图感觉把这个概念描述很清楚了
Convariance (协变)、Contravariance(逆变)以及Producter(供应者)、Consumer(消费者)
右侧协变,Metal extends Music ,作为生产者 金属乐手 MetalPlayer extends 乐手Player,
乐手 有 Collection<? super Music> act(),所以 金属乐手 act 生产出来的都是 Metal
abstract class Music{}
class MetalMusic extends Music{}
abstract class Player{
public abstract Collection<? super Music> act();
}
class MetalPlayer extends Player {
public Collection<? super Music> act(){
Collection<? super Music> result =new ArrayList<>();
result.add(new MetalMusic());
return result;
}
}
左侧逆变, 竹子 extends 蔬菜 ,作为消费者 熊猫 extends 食草动物
食草动物有 eat( ? extends 蔬菜 ) ,所以熊猫 eat(竹子) ,熊猫消费了竹子
abstract class Vegetable{}
class Bamboo extends Vegetable{}
abstract class Vegetarian{
public abstract void eat(Collection<? extends Vegetable> vegetables);
}
class Panda extends Vegetarian{
public void eat(Collection<? extends Vegetable> vegetables) {
for(Vegetable vegetable: vegetables) {
System.out.println("panda eat "+vegetable);
}
}
}
代码1 - 盘子装苹果的故事
然后我们再来看下 实际使用的情况,这里简单的用下 盘子装苹果的故事来说明下 JAVA T 这个泛型的情况。
这里盘子既作为放置苹果的消费者,又可以作为一个提供苹果的生产者的情况。
先上个基本类,假定盘子可以装卸任何水果,苹果继承了水果
//水果
class Fruit {
}
//苹果
class Apple extends Fruit {
}
//盘子
class Plate<T> {
T t;
public Plate(T t) {
this.t = t;
}
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
}
然后看下使用具体的使用,ERROR 部分代码都是 错误使用,导致编译器类型错误的内容。各位要注意相应的使用。
/**
* Producer Extends Consumer Super
* 盘子装苹果的故事
* @author zhangb
*
*/
public class PECS {
public static void main(String[] args) {
// Plate<Fruit> plate01 = new Plate<Apple>(new Apple()); // ERROR: Type mismatch
Plate<Fruit> plate02 = new Plate<Fruit>(new Apple());
// extends 上界操作符
Plate<? extends Fruit> plate11 = new Plate<Fruit>(new Fruit());
Plate<? extends Fruit> plate12 = new Plate<Fruit>(new Apple());
Plate<? extends Fruit> plate13 = new Plate<Apple>(new Apple());
// plate13.set(new Fruit()); // ERROR
// plate13.set(new Apple()); // ERROR
// Apple apple = plate13.get(); //ERROR
Fruit fruit = plate13.get();
Apple apple = (Apple) plate13.get();
// super 下界操作符
Plate<? super Fruit> People1 = new Plate<Fruit>(new Fruit());
Plate<? super Fruit> People2 = new Plate<Fruit>(new Apple());
// Plate<? super Fruit> People3 = new Plate<Apple>(new Apple()); //ERROR
// Fruit fruit1 = People2.get();//ERROR
Fruit fruit2 = (Fruit) People2.get();
People2.set(new Fruit());
People2.set(new Apple());
}
}
代码2 - 上下公车的故事
最后再用一个上下公车的故事来做个结尾,用来描述PECS原则下正确使用场景
至于公车为什么是Stack,不是 Queue,各位就自行脑补吧
/**
* 人
*
* @author zhangb
*/
class People<T> {
T t;
public People(T t) {
this.t = t;
}
// BAD CODE
// public void pushStackE(Stack<? extends T> bus) {
// bus.push(t); //ERROR
// }
// public T popStackS(Stack<? super T> bus) {
// return bus.pop(); //ERROR
// }
/**
* 上车
*
* @param bus
* 车
* @return 某人上车后的车
*/
public Stack<? super T> pushStackS(Stack<? super T> bus) {
bus.push(t);
return bus;
}
/**
* 下车
*
* @param bus
* 车
* @return 人
*/
public T popStackE(Stack<? extends T> bus) {
return bus.pop();
}
}
部分参考用资料
http://www.cnblogs.com/stevenshen123/p/9215750.html
https://itimetraveler.github.io/2016/12/27/【Java】泛型中 extends 和 super 的区别?/