java语言是符合里氏替换原则的,即任何基类可以出现的地方,子类一定可以出现。

 

为了获取更大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型,PS:通配符用于声明形参,不能用于类和创建对象上。

 

声明上下界

  • list<? extends Number>:定义下界,说明List中可能包含的元素类型是Number及其子类
  • List<? super Number>:定义上界,则说明List中包含的是Number及其父类

PECS原则

PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。

生产者

假如从列表或者迭代器中get T类型的元素,需要声明成<? extends T>,比如List< ? extends Integer>,此时可以保证从参数get到的元素至少是T。

消费者

假如如把T类型的元素 put 到参数(列表)中,需要把这个参数声明成< ? super T>,比如List<? super Integer>,此时可以保证put T类型到参数中。

既是生产者又是消费者

不要使用泛型

 

例子

下面是一个简单的Stack的API接口:

public class Stack<E> {
       public Stack();

       public void push(E e);
       public E pop();
       public boolean isEmpty();
}

 

假设想增加一个方法,按顺序将一系列元素全部放入Stack中,你可能想到的实现方式如下:

//参数src生产实例(生产者,输出对象),保证src里面的对象是stack对象的子类

public void pushAll(Iterable<? extends E> src) {

       for (E e : src)
           push(e);
}

 

 

与之对应的是:假设有一个方法popAll()方法,从Stack集合中弹出每个元素,添加到指定集合中去。

//参数dst消费stack里面的值(消费者,消费对象),保证stack里面的对象是dst的父类

public void popAll(Collection<? super E> dst) {

       while (!isEmpty())
           dst.add(pop());
}

 

JDK里面的代码

 

/**
 * Copies all of the elements from one list into another.  After the
 * operation, the index of each copied element in the destination list
 * will be identical to its index in the source list.  The destination
 * list must be at least as long as the source list.  If it is longer, the
 * remaining elements in the destination list are unaffected. <p>
 *
 * This method runs in linear time.
 *
 * @param  <T> the class of the objects in the lists
 * @param  dest The destination list.
 * @param  src The source list.
 * @throws IndexOutOfBoundsException if the destination list is too small
 *         to contain the entire source List.
 * @throws UnsupportedOperationException if the destination list's
 *         list-iterator does not support the <tt>set</tt> operation.
 */
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();
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

 

 总结

  • 如果你是想遍历collection,并对每一项元素操作时,此时这个集合时生产者(生产元素),应该使用 Collection<? extends Thing>.
  • 如果你是想添加元素到collection中去,那么此时集合时消费者(消费元素)应该使用Collection<? super Thing>