首页 > 编程知识 正文

java 泛型 PECS准则,java泛型属性

时间:2023-05-03 07:44:42 阅读:225859 作者:1922

  我们知道<?>表示:我想使用Java泛型来编写代码,而不是用原生类型;但是在当前这种情况下,我并不能确定下泛型参数的具体类型,因此用?表示任何某种类型。因此,根据我们对通配符的了解,使用无界通配符的泛型类不能够写数据,而在读取数据时,所赋值的引用也只能是 Object 类型。那么,我们究竟如何向泛型类写入、读取数据呢?

  《Effective Java2》给出了答案。 PECS: producer(读取)-extends, consumer(写入)-super。换句话说,如果输入参数表示一个 T 的生产者,就使用<? extends T>;如果输入参数表示一个 T 的消费者,就使用<? super T>。总之,通配符类型可以保证方法能够接受它们应该接受的参数,并拒绝那些应该拒绝的参数。 比如,一个简单的 Stack API :

public class Stack<E>{ public Stack(); public void push(E e); public E pop(); public boolean isEmpty();}

  现在要实现pushAll(Iterable src)方法,将实现 Iterable 接口的 src 的元素逐一入栈:

public void pushAll(Iterable<E> src){ for(E e : src) push(e)}

  那么问题就来了:假设有一个实例化Stack的对象stack(类型参数被实例化为Number),显然, 我们向这个 stack 中加入Integer型或Float型元素都是可以的,因为这些元素本来就是Number型的。因此,src 就包括但不限于 Iterable与Iterable两种可能。这时,在调用上述pushAll方法时,编译器就会产生type mismatch错误。原因是显而易见的,因为Java中泛型是不变的,Iterable 与 Iterable 都不是 Iterable及其子类型中的一种。所以,我们对pushAll方法的设计就存在逻辑上的问题。因此,应改为

// Wildcard type for parameter that serves as an E producerpublic void pushAll(Iterable<? extends E> src) { for (E e : src) push(e);}

  这样,我们就可以实现将实现Iterable接口的E类型的容器中的元素读取到我们的 Stack 中。

  那么,如果现在要实现popAll(Collection dst)方法,将 Stack 中的元素依次取出并添加到 dst 中,如果不用通配符实现:

// popAll method without wildcard type - deficient!public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); }

  同样地,假设有一个实例化Stack 的对象 stack , dst 为 Collection,显然,这是合理的。但如果我们调用上述的 popAll(Collection dst)方法,编译器会报出type mismatch错误,编译器不允许我们进行这样的操作。原因是显而易见的,因为Collection不是Collection及其子类型的一种。所以,我们对popAll方法的设计就存在逻辑上的问题。因此,应改为

// Wildcard type for parameter that serves as an E consumerpublic void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop());}

  这样,我们就可以实现将Stack中的元素读取到我们的 Collection 中。在上述例子中,在调用 pushAll方法时,src生产了E实例(produces E instances);在调用popAll方法时 dst 消费了E实例(consumes E instances)。Naftalin与Wadler将PECS称为Get and Put Principle

  此外,我们再来学习一个例子:java.util.Collections的copy方法(JDK1.7),它的目的是将所有元素从一个列表(src)复制到另一个列表(dest)中。显然,在这里,src是生产者,它负责产生T类型的实例;dest是消费者,它负责消费T类型的实例。这完美地诠释了PECS:

// List<? extends T> 类型的 src 囊括了所有 T类型及其子类型 的列表 // List<? super T> 类型的 dest 囊括了所有可以将 src中的元素添加进去的 List种类 public static <T> void copy(List<? super T> dest, List<? extends T> src) { // 将 src 复制到 dest 中 int srcSize = src.size(); if (srcSize > dest.size()) throw new IndexOutOfBoundsException("Source does not fit in dest"); if (srcSize < COPY_THRESHOLD || (src instanceof RandomAccess && dest instanceof RandomAccess)) { for (int i=0; i<srcSize; i++) dest.set(i, src.get(i)); } else { ListIterator<? super T> di=dest.listIterator(); ListIterator<? extends T> si=src.listIterator(); for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } }}

  因此,输入参数是生产者时,用 ? extends T;输入参数是消费者时,用 ? super T;输入参数既是生产者又是消费者时,那么通配符类型没什么用了:因为你需要的是严格类型匹配,这是不用任何通配符而得到的。无界通配符<?> 既不能做生产者(读出来的是Object),又不能做消费者(写不进去)。

转自 https://blog.csdn.net/justloveyou_/article/details/52420071

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。