Java泛型之通配符

原文點此連接javascript

使用通配符的緣由:Java中的數組是協變的,可是泛型不支持協變。php

數組的協變

首先了解下什麼是數組的協變,看下面的例子:java

Number[] nums = new Integer[10]; // OK

由於Integer是Number的子類,一個Integer對象也是一個Number對象,因此一個Integer的數組也是一個Number的數組,這就是數組的協變。數組

Java把數組設計成協變的,在必定程度上是有缺陷的。由於儘管把Integer[]賦值給Number[],Integer[]能夠向上轉型爲Number[],可是數據元素的實際類型是Integer,只能向數組中放入Integer或者Integer的子類。若是向數組中放入Number對象或者Number其餘子類的對象,對於編譯器來講也是能夠經過編譯的。可是運行時JVM可以知道數組元素的實際類型是Integer,當其它對象加入數組是就會拋出異常(java.lang.ArrayStoreException)。安全

泛型的設計目的之一就是保證了類型安全,讓這種運行時期的錯誤在編譯期就能發現,因此泛型是不支持協變的。例如:數據結構

List<Number> nums = new ArrayList<Integer>(); // incompatible types

當確實須要創建這種向上轉型的類型關係的時候,就須要用到泛型的通配符特性了。例如:測試

List<? extends Number> nums = new ArrayList<Integer>(); // OK

無邊界通配符(Unbounded Wildcards)

語法:

class-name<?> var-nameui

例子:

public static void print(List<?> list) {
    for (Object obj : list) {
        System.out.println(o);
    }
}

List<?> list和List list的區別:

  • List<?> list是表示持有某種特定類型對象的List,可是不知道是哪一種類型;List list是表示持有Object類型對象的List。
  • List<?> list由於不知道持有的實際類型,因此不能add任何類型的對象,可是List list由於持有的是Object類型對象,因此能夠add任何類型的對象。
    注意:List<?> list能夠add(null),由於null是任何引用數據類型都具備的元素。

Pair<?> 和 Pair 的區別

  • Pair<?>的  ? getFirst()方法,返回值只能賦值給一個Object對象,它的void setFirst(? )方法不能被調用,甚至不能用Object調用。
  • 爲何要使用這樣脆弱的類型?它對於許多簡單的操做很是有用。例如 ,下面這個方法將用來測試一個 pair 是否包含一個 mill 引用,它不須要實際的類型。
    public static boolean hasNulls (Pair<?> p)
    {
        return p.getFirstO = null | | p.getSecondO = null ;
    }
    經過將 hasNulls 轉換成泛型方法,能夠避免使用通配符類型:
    public static <T> boolean hasNulls (Pair<T> p)
    可是,帶有通配符的版本可讀性更強。

     


上邊界限定的通配符(Upper Bounded Wildcards)

語法:

class-name<? extends superclass> var-namespa

例子:

public static double sum(List<? extends Number> list) {
    double s = 0.0;
    for (Number num : list) {
        s += num.doubleValue();
    }
    
    return s;
}

List<? extends Number> list = new ArrayList<Integer>(); // OK
List<? extends Number> list = new ArrayList<Object>(); // error

特性:

  • List<? extends Number> list表示某種特定類型(Number或者Number的子類)對象的List。跟無邊界通配符同樣,由於沒法肯定持有的實際類型,因此這個List也不能add除null外的任何類型的對象
list.add(new Integer(1)); // error
list.add(null); // OK
  • 從list中獲取對象是是能夠的(好比get(0)),由於在這個List中,無論實際類型是什麼,但確定都能轉型爲Number。
Number n = list.get(0); // OK
Integer i = list.get(0); // error
  • 事實上,只要是形式參數有使用類型參數的方法,在使用無邊界或者上邊界限定的通配符的狀況下,都不能調用。好比以java.util.ArrayList爲例:
public E get(int index) // 能夠調用 public int indexOf(Object o) // 能夠調用 public boolean add(E e) // 不能調用 

下邊界限定的通配符(Lower Bounded wildcards)

語法:

class-name<? super subclass> var-name設計

例子:

public static void writeTo(List<? super Integer> list) {
    // ...
}

List<? super Number> list = new ArrayList<Number>(); // OK
List<? super Number> list = new ArrayList<Object>(); // OK
List<? super Number> list = new ArrayList<Integer>(); // error

特性:

  • List<? super Integer> list表示某種特定類型(Integer或者Integer的父類)對象的List。能夠肯定這個List持有的對象類型確定是Integer或者其父類,因此往list裏面add一個Integer或者其子類的對象是安全的,由於Integer或者其子類的對象均可以向上轉型爲Integer的父類對象。可是由於沒法肯定實際類型,因此往list裏面add一個Integer的父類對象是不安全的
list.add(new Integer(1)); // OK
list.add(new Object()); // error
  • 當從List<? super Integer> list獲取具體的數據的時候,JVM在編譯的時候知道實際類型能夠是任何Integer的父類,因此爲了安全起見,要用一個最頂層的父類對象來指向取出的數據,這樣就能夠避免發生強制類型轉換異常了。
Object obj = list.get(0); // OK
Integer i = list.get(0); // error

PECS原則(Producer Extends Consumer Super)

從上面上邊界限定的通配符和下邊界限定的通配符的特性,能夠知道:

  • 對於上邊界限定的通配符,沒法向其中加入任何對象,可是能夠從中正常取出對象。
  • 對於下邊界限定的通配符,能夠存入subclass對象或者subclass的子類對象,可是取出時只能用Object類型變量指向取出的對象。

簡而言之,上邊界限定(extends)的通配符適合於內容的獲取,而下邊界限定(super)的通配符更適合於內容的存入。因此就有了一個PECS原則來很好的解釋這兩種通配符的使用原則。

  • 當一個數據結構做爲producer對外提供數據的時候,應該只能取數據而不能存數據,因此適合使用上邊界限定(extends)的通配符。
  • 當一個數據結構做爲consumer獲取並存入數據的時候,應該只能存數據而不能取數據,因此適合使用下邊界限定(super)的通配符。
  • 若是既須要取數據也須要存數據,就不適合使用泛型的通配符。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}
相關文章
相關標籤/搜索