如今问题来了:咱们何时用extends何时用super呢?《Effective Java》给出了答案:java
PECS: producer-extends, consumer-super安全
好比,一个简单的Stack API:dom
public class Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); }
要实现pushAll(Iterable<E> src)方法,将src的元素逐一入栈:spa
public void pushAll(Iterable<E> src){ for(E e : src) push(e) }
假设有一个实例化Stack<Number>的对象stack,src有Iterable<Integer>与 Iterable<Float>;code
在调用pushAll方法时会发生type mismatch错误,由于Java中泛型是不可变的,Iterable<Integer>与 Iterable<Float>都不是Iterable<Number>的子类型。对象
所以,应改成ip
// Wildcard type for parameter that serves as an E producer public void pushAll(Iterable<? extends E> src) { for (E e : src) // out T, 从src中读取数据,producer-extends push(e); }
要实现popAll(Collection<E> dst)方法,将Stack中的元素依次取出add到dst中,若是不用通配符实现:ci
// popAll method without wildcard type - deficient! public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); }
一样地,假设有一个实例化Stack<Number>的对象stack,dst为Collection<Object>;get
调用popAll方法是会发生type mismatch错误,由于Collection<Object>不是Collection<Number>的子类型。it
于是,应改成:
// Wildcard type for parameter that serves as an E consumer public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop()); // in T, 向dst中写入数据, consumer-super }
Naftalin与Wadler将PECS称为 Get and Put Principle。
在java.util.Collections
的copy
方法中(JDK1.7)完美地诠释了PECS:
public static <T> void copy(List<? super T> dest, List<? extends T> src) { 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(); // in T, 写入dest数据 ListIterator<? extends T> si=src.listIterator(); // out T, 读取src数据 for (int i=0; i<srcSize; i++) { di.next(); di.set(si.next()); } } }
正如上文所讲的,在 Java 泛型里,有通配符这种东西,咱们要用? extends T
指定类型参数的上限,用 ? super T
指定类型参数的下限。
而Kotlin 抛弃了这个东西,引用了生产者和消费者的概念。也就是咱们前面讲到的PECS。生产者就是咱们去读取数据的对象,消费者则是咱们要写入数据的对象。这两个概念理解起来有点绕。
咱们用代码示例简单讲解一下:
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... ListIterator<? super T> di = dest.listIterator(); // in T, 写入dest数据 ListIterator<? extends T> si = src.listIterator(); // out T, 读取src数据 ... }
List<? super T> dest
是消费(方法产生)数据的对象,这些数据会写入到该对象中,这些数据该对象被“吃掉”了(Kotlin中叫in T
)。
List<? extends T> src
是(为方法)提供数据的对象。这些数据哪里来的呢?就是经过src读取得到的(Kotlin中叫out T
)。
out T
与 in T
在Kotlin中,咱们把那些只能保证读取数据时类型安全的对象叫作生产者,用 out T
标记;把那些只能保证写入数据安全时类型安全的对象叫作消费者,用 in T
标记。
若是你以为太晦涩难懂,就这么记吧:
out T
等价于? extends T
in T
等价于? super T
此外, 还有*
等价于?