PECS
现在问题来了:我们什么时候用extends什么时候用super呢?《Effective Java》给出了答案:
PECS: producer-extends, consumer-super
比如,一个简单的Stack API:
public class Stack<E>{ public Stack(); public void push(E e): public E pop(); public boolean isEmpty(); }
要实现pushAll(Iterable<E> src)方法,将src的元素逐一入栈:
public void pushAll(Iterable<E> src){ for(E e : src) push(e) }
假设有一个实例化Stack<Number>的对象stack,src有Iterable<Integer>与 Iterable<Float>;
在调用pushAll方法时会发生type mismatch错误,因为Java中泛型是不可变的,Iterable<Integer>与 Iterable<Float>都不是Iterable<Number>的子类型。
因此,应改为
// 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中,如果不用通配符实现:
// popAll method without wildcard type - deficient! public void popAll(Collection<E> dst) { while (!isEmpty()) dst.add(pop()); }
同样地,假设有一个实例化Stack<Number>的对象stack,dst为Collection<Object>;
调用popAll方法是会发生type mismatch错误,因为Collection<Object>不是Collection<Number>的子类型。
因而,应改为:
// 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()); } } }
6.3 Kotlin的泛型特色
正如上文所讲的,在 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
)。
6.3.1 out T
与 in T
在Kotlin中,我们把那些只能保证读取数据时类型安全的对象叫做生产者,用 out T
标记;把那些只能保证写入数据安全时类型安全的对象叫做消费者,用 in T
标记。
如果你觉得太晦涩难懂,就这么记吧:
out T
等价于? extends T
in T
等价于? super T
此外, 还有*
等价于?