Kotlin极简教程中对? extends 和 ? super (out和int)的描述

PECS

如今问题来了:咱们何时用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.Collectionscopy方法中(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 此外, 还有 * 等价于 ?

相关文章
相关标签/搜索