Java中的逆變與協變 很直接不饒彎的講出來了
阿新 • • 發佈:2018-12-15
extends row 答案 void card erro 類對象 str ext
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中的逆變與協變 很直接不饒彎的講出來了