Java中的逆变与协变 很直接不饶弯的讲出来了

http://blog.csdn.net/z69183787/article/details/51598345

看下面一段代码:
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以外的任意对象。可以这样理解一下,(你想如果list1能添加Integer ,list2能添加float 他们的父类都是List<? extends Number> 那么将来如果我写成 list001.set(0,0.04);是不是也可以了 因为 list001.get(0)返回的是引用是Number类型,
但实际的类型是Integer类型,这里内存就不兼容了,为了防止这种情况的发生,所以这种用法就被java禁止了,那么协变还有什么用? 对也就只剩下接类型了如 List<? extends Number> list001 = new ArrayList<Integer>();不过却可以添加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.
       比如,一个简单的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());
        }
    }
}

原文地址:https://www.cnblogs.com/kexb/p/10124412.html

时间: 2024-07-30 10:19:51

Java中的逆变与协变 很直接不饶弯的讲出来了的相关文章

Java中的逆变与协变(转)

看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch List<? extends Number> list = new ArrayList<Number>(); list.add(new Integer(1)); //error list.add(new Float(1.2f)); //error 有人会

Java中的逆变与协变

看下面一段代码 Number num = new Integer(1); ArrayList<Number> list = new ArrayList<Integer>(); //type mismatch List<? extends Number> list = new ArrayList<Number>(); list.add(new Integer(1)); //error list.add(new Float(1.2f)); //error 有人会

Java中的逆变和协变

// 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&

OOP中的逆变和协变

逆变和协变在存在于强类型语言中,尽管非常少提及,可是里面蕴含了面向对象的世界观.感谢和我一起讨论这个问题的人. 这里用了C#.Scala的语法作为演示样例,事实上逆变和协变的概念跟语言本身关系不大,事实也是如此. 一.定义 逆变的參数能够由指定的类型的子类型取代,协变的參数能够由指定类型的父类型取代. Scala中的逆变声明:Function[-A,+B] ;当中泛型-A为逆变类型.在实例化时,能够使用A类型或者A类的子类型. 二.协变与逆变的用途不同 1.语义 Scala中,函数的原型之中的一

Java 逆变与协变

最近一直忙于学习模电.数电,搞得头晕脑胀,难得今天晚上挤出一些时间来分析一下Java中的逆变.协变.Java早于C#引入逆变.协变,两者在与C#稍有不同,Java中的逆变.协变引入早于C#,故在形式没有C#直观(Google推出的基于jvm的Kotlin语音,则完全走向了C#的路线).Java中逆变.协变,在泛型集合使用中更多些.更直观(像C#中的用法在Java中较少出现,但并非不可). 正常泛型集合的使用 示例代码如下: public static void main(String[] arg

java逆变与协变(待完善)

协变:若B是A的子类,且F(B)是F(A)的子类,则F为协变 逆变:若B是A的子类,且F(B)是F(A)的父类,则F为逆变 java中的协变:B是A的子类,则List是List的子类 java中的逆变:B是A的子类,则List是List的子类 java中协变与逆变的约束:java中的协变逆变和约束,都是出于对多态的应用. 多态:同一个接口,使用不同的实例执行不同的操作. 协变约束:协变方法支持对传入参数的读操作,但不支持修改操作 逆变约束: 原文地址:https://www.cnblogs.co

Scala 深入浅出实战经典 第81讲:Scala中List的构造是的类型约束逆变、协变、下界详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-97讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/ceac2IoB-ik/优酷:http://v.youku.com/v_show/id_

Scala中List的构造是的类型约束逆变、协变、下界详解

学习了Scala中List的构造是的类型约束逆变.协变.下界详解,列表中用::加入父类的对象,列表会协变为父类,例子如下: Def :: [B>:A](x:B):List(B)= New scala:collection.imutable.::(x,this) 王家林亲授<DT大数据梦工厂>大数据实战视频“Scala深入浅出实战经典”视频.音频和PPT下载!第81讲:Scala中List的构造是的类型约束逆变.协变.下界详解腾讯微云:http://url.cn/UNeLA2百度云盘:ht

C# 逆变与协变

原文:C# 逆变与协变 该文章中使用了较多的 委托delegate和Lambda表达式,如果你并不熟悉这些,请查看我的文章<委托与匿名委托>.<匿名委托与Lambda表达式>以便帮你建立完整的知识体系. 在C#从诞生到发展壮大的过程中,新知识点不断引入.逆变与协变并不是C#独创的,属于后续引入.在Java中同样存在逆变与协变,后续我还会写一篇Java逆变协变的文章,有兴趣的朋友可以关注一下. 逆变与协变,听起来很抽象.高深,其实很简单.看下面的代码: class Person {