PECS与泛型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ls0111/article/details/82023275

引言

第一次看到PECS原则是在《阿里巴巴java开发手册》,看的我一头雾水。估计自己理解力太差了吧。。。。再次接触是在阅读《 java编程思想》的时候。虽然这本书是基于jdk1.5编写的,有部分内容已不再适用现在新的jdk版本。但是我要引用一句高中数学老师的“名言”,几乎每次讲课都能够听到:“我讲的不是数学,是‘思想’!”。《java编程思想》更是如此,讲了很多编程语言都会遇到问题,是站在语言设计的高度来分析java的。额。。。扯得有点远,下面来说正题。

正文

什么是PECS?
英文是:Producer extends Consumer super,即是生产者用extends,消费者用super。What?只看这些我也不懂。先从数组的协变性讲起。
那什么又是数组协变性呢?简单的说,Base是Sub的基类,那么Base[ ]数组也是Sub[ ]数组的基类。即,可以使用Base[ ]数组的引用指向Sub[ ]数组的对象。听起来是没有什么问题,对吧?在早期没有泛型的时候,JDK源码实现ArrayList的构造器中会把调用集合的toArray()方法生成的数组赋值给elementData的Object数组。这就出现了问题:

	public static void main(String[] args) throws Exception {
		Object[] objArr = new String[5];
		objArr[0] = 100;
	}

这样编写代码,编译期是不会发出任何警告的。但是运行后会抛异常:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer

也就是说,数组能存放的数据类型,仅和数组初始化时分配的数据类型一致。

泛型的出现在一定程度上可以帮助程序员在代码编译期就能发现此类错误,及早更正代码。由于泛型是在半道上“杀出”,导致其在功能上不是十分完备。因为jdk需要兼容先前的一些代码,不能彻底改造源码实现,所以java设计者采用了目前java这种折中处理的方法。详细信息可以自行查看《java编程思想》原文。

当代码使用上述数组的方式进行赋值操作时,编译期会编译报错:

	public static void main(String[] args) throws Exception {
		List<Object> list = new ArrayList<String>();
		// Type mismatch: cannot convert from ArrayList<String> to List<Object>
	}

终于要说PECS这个原则了。此时若要上述代码不报错,需要如何处理呢?

	public static void main(String[] args) throws Exception {
		List<? extends Object> list = new ArrayList<String>();
	}

没错,就是使用extends关键字!而此时的通配符**?代表只要是Object对象或者其子类就可以了。规定了List集合中元素的上限**。

这时候就能显现出Java泛型不完善的地方,如果此时要向list中添加元素,就会报错。无论是什么类型的元素(null除外,该值也无意义)。 因为仅仅限制了上限,但是“无下限”啊!就是可以向集合添加任何Object的子类,这时Java设计者只能禁止添加任何元素来避免这样的错误出现。


先提前说明一下本文中生产者与消费者的含义,以防止大家看的蒙B,我写的也蒙B。生产者与消费者都是针对使用泛型的集合或类来说的,生产即是该集合进行生产,对该例子而言就是调用list.get()方法获取集合元素。消费即是该集合进行消费,对该例子而言就是调用list.add()方法向集合中添加元素。


所以才有PE的说法,即Producer extends。其实是说**? extends**类型的泛型只能作为生产者。生产者的说法是针对使用该类泛型的对象来说的。举个例子:

扫描二维码关注公众号,回复: 4749253 查看本文章
	public static void main(String[] args) throws Exception {
		List<? extends Object> list = new ArrayList<String>();
		list.add(null);// compile correct
		list.add(1);// compile error
		list.add(new Object());// compile error
		
		Object obj = list.get(0);
	}

因为已经定义该集合元素的上限,最大该处的“最大”与下文中的“最小”是相对于继承结构的类图上下关系而言的,越向上越大,向下越小。类似于向上或向下转型)也就是Object。所以在取出(或者说是list生产出元素)元素的时候,使用Object来接收是没有问题的。那如果我们想要向集合中添加元素又该怎么办呢?继续看例子:

public class Test {
	
	public static void main(String[] args) throws Exception {
		List<? super Fruit> list = new ArrayList<Fruit>();
		list.add(new Fruit());// compile correct
		list.add(new Apple());// compile correct
		list.add(new Food());// compile error
	}
}
class Food {}
class Fruit extends Food {}
class Apple extends Fruit {}

简单的创建了食物、水果、苹果的类关系,不再赘述。此时类型参数为? super Fruit,定义了下限。代表此类型参数都是Fruit或者Fruit的父类。那么在向集合中添加元素的时候,该集合可以接收最小也是Fruit的元素,最大就不好言说了。所以向该集合添加Fruit以及Fruit子类的元素是被允许的。铛!铛!铛!CS出现了,Consumer super 原则说明需要向集合中添加元素的时候要使用**? super**来限制类型参数。

说道这里,也差不多结束了。不知道我有没有讲清楚自己所理解的PECS,如果有讲的不到位或者有问题的地方,敬请各位大神指出!不吝赐教!

希望与大家共同学习,共同进步!

猜你喜欢

转载自blog.csdn.net/ls0111/article/details/82023275