JAVA泛型與類型安全

1. 基礎泛型

//定義泛型類,接口的定義和類一樣
class A<T,P extends Number> {
  T t;
  P p;// extends 限定泛型上界
  public <M> M doSomething(M params) {}
}

關於泛型的使用基本如此,開發者可以在實際使用時靈活地指定泛型所對應的實際類型,從而達到不同的效果

2. 協變與逆變與不變

  • 協變

在Java中的數組是協變的,簡單來說即有:

A extends B  ==>  A[] extends B[]
B[] bs = new A[2]; //合法
  • 逆變

與協變相對,逆轉了類型關係

  • 不變

Java 語言中的泛型基本上完全在編譯器中實現,由編譯器執行類型檢查和類型推斷,然後生成普通的非泛型的字節碼。這種實現技術稱爲擦除(erasure)(編譯器使用泛型類型信息保證類型安全,在生成字節碼之前將其清除,因此運行時無法判斷泛型的具體類型)

由於泛型擦除的原因,Java中的泛型是不變的,舉一個栗子可以簡明的說明這個問題,比如

A extends B    
C extends B
List<B> bls = new ArrayList<A>();//類似數組的寫法不合法,編譯器檢查不通過

爲了解決泛型的不變,就需要用到通配符?與extends、super。

3. 通配符? 與 extends 與 super

先以泛型集合List<E>爲例來說明以上三個關鍵字的特性
(1)通配符?在單獨使用時可以看作是? extends Object

List<?> l0= new ArrayList<Object>();

(2)? extends xxx使得泛型具有協變的特性,extends限定了泛型的上界

//extends包含指向類及其子類
List<? extends Number> l1= new ArrayList<Number>();
List<? extends Number> l2= new ArrayList<Integer>();

那麼狗二蛋,代價是什麼呢?代價就是,此時泛型實例只能作爲輸出

l1.add(new Integer(1));//無法編譯
Number n = l1.get(0);//可以獲取到

(3)? super xxx使得泛型具有逆變的特性,super限定了泛型的下界

//super包含指向類及其父類
List<? super Integer> l3 = new ArrayList<Integer>();
List<? super Integer> l4 = new ArrayList<Number>();

那麼狗二蛋,代價是什麼呢?代價就是,此時泛型實例只能作爲輸入(但是由於泛型上界可以認爲是Object,因此是可以輸出到Object的,但是這毫無意義)

l3.add(new Integer(1));//可以編譯
Object o = l3.get(0);//可以獲取到

總結一點就是:寫入需要有類型下界,而讀出需要有類型上界

4. 在Android中的實例

在Android中,一個典型的例子就是RxJava的map操作

public final <R> Observable<R> map(Function<? super T, ? extends R> mapper) {
    ObjectHelper.requireNonNull(mapper, "mapper is null");
    return RxJavaPlugins.onAssembly(new ObservableMap<T, R>(this, mapper));
}

public interface Function<T, R> {
    /**
     * Apply some calculation to the input value and return some other value.
     * @param t the input value
     * @return the output value
     * @throws Exception on error
     */
    R apply(@NonNull T t) throws Exception;
}

這裏泛型T作爲泛型下界,承接上流的輸入,泛型R則作爲泛型上界,呈遞輸出到下游。由於上界和下界使用泛型定義,所以實際傳入的類型不會被固定

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章