Java中的逆变与协变

转载自:http://blog.csdn.net/z69183787/article/details/51598345

看下面一段代码:

 

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

List<? extends Number> list = new ArrayList<Number>();
list.add(new Integer(1)); //error

为什么Number的对象可以由Integer实例化,而ArrayList<Number>的对象却不能由ArrayList<Integer>实例化?list中的<? extends Number>声明其元素是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中泛型是不变的,可有时需要实现逆变与协变,怎么办呢?这时就需要通配符?。
       <? extends>实现了泛型的协变,比如:

 

 

 

List<? extends Number> list = new ArrayList<>();

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

 

List<? extends Number> list001 = new ArrayList<Integer>();
List<? extends Number> list002 = new ArrayList<Float>();

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

       <? super>实现了泛型的逆变,比如:

 

List<? super Number> list = new ArrayList<>();

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

 

List<? super Number> list001 = new ArrayList<Number>();
List<? super Number> list002 = new ArrayList<Object>();
list001.add(new Integer(3));
list002.add(new Integer(3));

 

3.PECS

       现在问题来了:究竟什么时候用extends什么时候用super呢?《Effective Java》给出了答案:
       PECS: producer-extends, consumer-super. (extends可以get,super可以put)
       比如,一个简单的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)
        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());
}

       在上述例子中,在调用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 <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());
        }
    }
}
  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
Java泛型协变逆变都是针对类型转换的规定。 协变(covariant):指的是继承链子类(派生类)类型能够作为父类(基类)类型的一种属性,也就是子类可以作为父类使用的能力。在泛型协变的概念可以用来表示如果类型A是类型B的一个子类型,那么泛型类G<A>就可以视作泛型类G<B>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口List public interface List<E> { void add(E e); E get(int index); } // 定义一个方法acceptList,其形参类型为List<? extends Animal> public static void acceptList(List<? extends Animal> list) { for (Animal animal : list) { // ... } } // List类型为List<Dog> List<Dog> list = new ArrayList<Dog>(); list.add(new Dog()); acceptList(list); // 在这里,我们可以传入一个List<Dog>参数,因为Dog类是Animal类的子类 ``` 逆变(contravariant):指的是继承链父类(基类)类型能够作为子类(派生类)类型的一种属性,也就是父类可以作为子类使用的能力。在泛型逆变的概念可以用来表示如果类型A是类型B的一个超类型,那么泛型类G<B>就可以视作泛型类G<A>的一个子类型。 例子: ```java // Animal类 public class Animal {} // Dog类是Animal类的子类 public class Dog extends Animal {} // 泛型接口Comparator public interface Comparator<T> { int compare(T o1, T o2); } // 定义一个方法sortList,其形参类型为List<? super Dog> public static void sortList(List<? super Dog> list) { // ... } // List类型为List<Animal> List<Animal> list = new ArrayList<Animal>(); list.add(new Animal()); sortList(list); // 在这里,我们可以传入一个List<Animal>参数,因为Animal类是Dog类的超类型 ``` extends和super关键字常常用于定义泛型类型参数的上边界(upper bound)和下边界(lower bound)。extends表示类型参数的上限,超过这个范围就会导致编译错误;super表示类型参数的下限,超过这个范围也会导致编译错误。 例子: ```java // 泛型类Pair,其类型参数T有上限(用extends)为Comparable<? super T>,表示类型T要么是Comparable<? super T>本身,要么是Comparable<? super T>的子类型 public class Pair<T extends Comparable<? super T>> { private T first; private T second; public Pair(T first, T second) { this.first = first; this.second = second; } public T getFirst() { return first; } public T getSecond() { return second; } public T max() { return first.compareTo(second) >= 0 ? first : second; } } // Pair类型为Pair<String> Pair<String> pair = new Pair<String>("hello", "world"); String max = pair.max(); // 在这里,我们可以调用max方法,因为String类实现了Comparable<String>接口 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值