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則作爲泛型上界,呈遞輸出到下游。由於上界和下界使用泛型定義,所以實際傳入的類型不會被固定