Java中的逆变与协变 很直接不饶弯的讲出来了
Posted 听哥哥的话
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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()); } } }
以上是关于Java中的逆变与协变 很直接不饶弯的讲出来了的主要内容,如果未能解决你的问题,请参考以下文章
[JAVA冷知识]什么是逆变(contravariant)与协变(covariant)?数组是否支持协变&逆变?泛型呢?
[JAVA冷知识]什么是逆变(contravariant)与协变(covariant)?数组是否支持协变&逆变?泛型呢?