PECS原则

PECS 原则

自从原来的 csdn 泄密事件后,csdn被清库后,就一直没再写CSDN了,今天就算是中秋开个章。

PECS 全称 Producter Extends, Consumer Super 原则,即
供应者使用 Extends,消费者使用 Super。这个原则其实质来源于 Liskov替换原则(LSP) ,即 所有出现基类(父类)的地方都可以用子类进行替换。

先回顾下LSP,LSP包含四层含义:

  1. 子类完全拥有父类的方法,且具体子类必须实现父类的抽象方法;
  2. 子类中可以增加自己的方法;
  3. 当子类覆盖或实现父类的方法时,方法的形参要比父类方法的更加宽松;
  4. 当子类覆盖或实现父类的方法时,方法的返回值要比父类方法的更加严格。

针对LSP四层含义的③④条,就引出了协变(Covariance)和逆变(Contravariance)的概念:

  1. 协变,简言之,就是父类型到子类型,变得越来越具体,在Java中体现在返回值类型不变或更加具体(异常类型也是如此)等。
  2. 逆变,简言之,就是父类型到子类型,变得越来越具体,但是方法的形参却变得更加抽象或不变。

可能各位看官看了更迷糊了,下一章节再用例子讲下。

描述

分享一个来自stackoverflow的图片: Alt
来源: 点击

上图感觉把这个概念描述很清楚了
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 的区别?/

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值