Java中的逆变与协变

看下面一段代码:

// public final class Integer extends Number
Number num = new Integer(1);  
List list = new ArrayList<>();
list.add(new Integer(3));
ArrayList list = new ArrayList(); //type mismatch
 
List list = new ArrayList();
list.add(new Integer(1)); //error

为什么Number的对象可以由Integer实例化,而ArrayList的对象却不能由ArrayList实例化?list中的声明其元素是Number或Number的派生类,为什么不能add Integer?为了解决这些问题,需要了解Java中的逆变和协变以及泛型中通配符用法。
 

1. 逆变与协变

Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:
       当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;
       当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;
       如果上面两种关系都不成立则叫做不可变。

2. 泛型中的通配符实现协变与逆变

JAVA中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要通配符?。
       实现了泛型的协变,比如:

List list = new ArrayList<>();

 

“? extends Number”则表示通配符”?”的上界为Number,换句话说就是,“? extends Number”可以代表Number或其子类,但代表不了Number的父类(如Object),因为通配符的上界是Number。
       于是有“? extends Number” ≦ Number,则List ≦ List< Number >。那么就有:


List list001 = new ArrayList();
List list002 = new ArrayList();

但是这里不能向list001、list002添加除null以外的任意对象。可以这样理解一下,List可以添加Interger及其子类,List可以添加Float及其子类,List、List都是List的子类型,如果能将Float的子类添加到List中,就说明Float的子类也是可以添加到List中的,显然是不可行。故java为了保护其类型一致,禁止向List添加任意对象,不过却可以添加null。

       实现了泛型的逆变,比如:

List list = new ArrayList<>();

“? super Number” 则表示通配符”?”的下界为Number。为了保护类型的一致性,因为“? super Number”可以是Object或其他Number的父类,因无法确定其类型,也就不能往List添加Number的任意父类对象。但是可以向List添加Number及其子类。

List list001 = new ArrayList();
List list002 = new ArrayList();
list001.add(new Integer(3));
list002.add(new Integer(3));

3.PECS

现在问题来了:究竟什么时候用extends什么时候用super呢?《Effective Java》给出了答案:
       PECS: producer-extends, consumer-super.
       比如,一个简单的Stack API:

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

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

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

假设有一个实例化Stack的对象stack,src有Iterable与 Iterable;在调用pushAll方法时会发生type mismatch错误,因为Java中泛型是不可变的,Iterable与 Iterable都不是Iterable的子类型。因此,应改为:

// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable src) {
    for (E e : src)
        push(e);
}

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

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

 同样地,假设有一个实例化Stack的对象stack,dst为Collection;调用popAll方法是会发生type mismatch错误,因为Collection不是Collection的子类型。因而,应改为:

// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection dst) {
    while (!isEmpty())
        dst.add(pop());
}

 在上述例子中,在调用pushAll方法时生产了E 实例(produces E instances),在调用popAll方法时dst消费了E 实例(consumes E instances)。Naftalin与Wadler将PECS称为Get and Put Principle。

       java.util.Collections的copy方法(JDK1.7)完美地诠释了PECS:

public static  void copy(List dest, List 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 di=dest.listIterator();
        ListIterator si=src.listIterator();
        for (int i=0; i

PECS总结:

  • 要从泛型类取数据时,用extends;
  • 要往泛型类写数据时,用super;
  • 既要取又要写,就不用通配符(即extends与super都不用)。

你可能感兴趣的:(java)