最近在看 Rxjava 與 Retrofit 袖订,發(fā)現(xiàn)它們都大量的使用到了泛型這個概念弧圆,之前我對泛型是有一點(diǎn)了解的铣焊,但是這點(diǎn)了解明顯在學(xué)習(xí)它們的時候不夠用,于是便花了點(diǎn)時間重新整體的學(xué)習(xí)了一遍泛型玄妈。
什么是泛型?###
泛型(generic)是指參數(shù)化類型的能力髓梅。
泛拟蜻,廣泛,按照字面意思可以簡單的理解為它的含義是廣泛的類型枯饿,事實(shí)也是如此酝锅,當(dāng)我們?yōu)榻涌凇⒎椒ɑ蛘哳惗x為泛型時奢方,就說明它們可以接受的類型是廣泛的搔扁,可以是 String ,int蟋字,date 等等稿蹲。我們也可以指定泛型的具體類型,這樣鹊奖,當(dāng)傳入的對象類型與我們指定的類型不相符時編譯器就會報(bào)錯苛聘,能讓代碼更為健壯。
一個簡單的例子:
public class GenericTest {
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add("張三");
list.add("李四");
list.add(new Integer(10));
for(int i = 0; i < list.size(); i++){
String name = (String) list.get(i);
System.out.println(name);
}
}
}
我們在 list 數(shù)組中加入了兩個 Sting 對象和 一個 int 對象忠聚,但在取出來時將它們?nèi)繌?qiáng)轉(zhuǎn)成 String 類型的焰盗,所以毫無疑問代碼會崩潰:
Exception in thread "main" 張三
李四
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at GenericTest.main(GenericTest.java:10)
我們簡單的修改一下代碼:
將 ArrayList list = new ArrayList();
改為 ArrayList<String> list = new ArrayList<String>();
這時,你指定了 list 的類型為 String 咒林,當(dāng)你為它添加了除 String 外的類型對象時熬拒,IDE 在你寫代碼的時候就會提示你出錯,而不會等到程序運(yùn)行時才報(bào)錯垫竞。
通常情況下澎粟,當(dāng)我們不指定 ArrayList 泛型的具體類型時蛀序,可以把它的泛型類型視為 Object,即所有對象活烙。也就是說徐裸,ArrayList 默認(rèn)是泛型類型,即 ArrayList<E> 啸盏,這樣我們就可以根據(jù)實(shí)際情況來指定 ArrayList 的具體類型重贺。
從這里我們可以看出,當(dāng)我們編寫的代碼實(shí)現(xiàn)了泛型后回懦,這種代碼就會變得更為通用气笙,因?yàn)樗梢栽诰唧w使用中根據(jù)實(shí)際要求來定義參數(shù)類型,這也正是泛型的定義:參數(shù)化類型怯晕。
泛型的使用###
- 泛型類:
public class GenericTest <T>{
private T a;
public GenericTest(T a){
this.a = a;
}
public T get(){
return a;
}
public static void main(String[] args){
GenericTest<String> string = new GenericTest<String>("hello,world");
GenericTest<Integer> integer = new GenericTest<Integer>(2016);
String wordString = (String) string.get();
int year = (Integer) integer.get();
System.out.println(wordString);
System.out.println(year);
}
}
這里潜圃,我們將 GenericTest 的類型設(shè)置為 <T>,即表示泛型舟茶,我們也可以用 E谭期,或 V 來表示“闪梗可以看到隧出,在主函數(shù)中,我們將 T 的類型分別指定為 String 和 Integer阀捅,然后再打印出來胀瞪,結(jié)果如下:
hello,world
2016
從這我們可以得到這樣一條通用語句:
`GenericTest<Xxx> xxx = new GenericTest<Xxx>(new Xxx());`
即 GenericTest 可以接受任何類型的對象。在這里也搓,我們?nèi)绻麑?T 改為 Object 的話赏廓,GenericTest 同樣能接受所有類型,但是如果假設(shè) GenericTest 的作用是個集合呢傍妒?如 ArrayList幔摸?相信區(qū)別大家一眼便能看出來。所以颤练,將類的類型指定為泛型而不是 Object 明顯能更適用于多場景既忆。
這里,我們需要注意的一點(diǎn)是嗦玖,雖然在編譯時 GenericTest<String> 與 GenericTest<Integer> 是兩種類型患雇,但實(shí)際上運(yùn)行時只有一個 GenericTest 類會被加載到JVM 中,如下所示:
System.out.println(wordString +"->" + string.getClass().getName());
System.out.println(year + "->" + integer.getClass().getName());
運(yùn)行結(jié)果為:
hello,world->GenericTest
2016->GenericTest
為什么呢宇挫?因?yàn)檎嬲\(yùn)行時泛型是不存在的苛吱,也就是說泛型只會在編譯時存在,在運(yùn)行時會被擦除器瘪。所以翠储, GenericTest 類在加載到 JVM 中的方式為:
public class GenericTest{
private Object a;
public GenericTest(Object a){
this.a = a;
}
public Object get(){
return a;
}
public static void main(String[] args){
GenericTest string = new GenericTest("hello,world");
GenericTest integer = new GenericTest(2016);
String wordString = (String) string.get();
int year = (Integer) integer.get();
System.out.println(wordString);
System.out.println(year);
}
}
即用 Object 來代替<T>绘雁,也就是說,泛型的意義在于能讓你在編寫的代碼在編譯時就檢查它的類型安全援所,并且它的類型轉(zhuǎn)換都是自動和隱式的庐舟,而用 Object 的類型轉(zhuǎn)換則是顯式轉(zhuǎn)換,并且當(dāng)真正的參數(shù)錯誤時不會提出警告住拭,如第一個例子挪略。當(dāng)然,泛型的真正意義遠(yuǎn)不如此滔岳,還需要我們繼續(xù)去深入學(xué)習(xí)杠娱。
- 泛型方法
public class GenericTest {
public static <T> void print(T t){
System.out.println(t + "->" + t.getClass().getName());
}
public static <T> T get(T t){
return t;
}
public static void main(String[] args){
GenericTest.print(2016);
GenericTest.print("hello,world");
System.out.println(GenericTest.get(2016));
System.out.println(GenericTest.get("hello,world"));
}
}
這是一個簡單的泛型方法,能夠做到和 GenericTest 泛型類一樣的效果澈蟆,它和泛型類的區(qū)別在于泛型類型`<T>`要放在方法返回類型之前墨辛,如:`<T> void print(T t)`卓研,而泛型類要放在類名后面趴俘,如:`GenericTest<T>`。既然它們效果都是一樣的奏赘,那具體使用時我們應(yīng)該用泛型類還是方法呢寥闪?
> 無論何時,只要你能做到磨淌,你就應(yīng)該盡量的使用泛型方法疲憋。
為什么呢?因?yàn)榉盒头椒ㄗ寙栴}變得更為具體梁只,而不是一個整體的對象缚柳。另外,對于靜態(tài)方法來說搪锣,它無法訪問泛型類的類型參數(shù)秋忙,所以,它想擁有泛型的能力构舟,就必須定義為泛型方法灰追。
- 泛型接口
public interface Comparable<T>{
public int compareTo(T o);
}
這個接口是 JDK 1.5之后自帶的一個比較接口,我們可以看出狗超,它的定義方式和泛型類的定義相差無幾弹澎,泛型類型`<T>`也是放在接口名后面,使用也基本沒什么差別努咐。
###通配泛型###
首先苦蒿,什么是通配泛型?為什么要使用到通配泛型呢渗稍?不急佩迟,我們先介紹一下通配泛型溃肪,通配泛型的類型有三種形式:`<?>`音五、`<惫撰? extens T>`、`<躺涝? super T>`厨钻,我們分別來講解一下它們的具體用法與意義。
- <?>
非受限通配符(unbounded wildcard)坚嗜,它與第二種方式其實(shí)可以歸為一類夯膀,因?yàn)樗淖饔孟喈?dāng)于是`? extends Object`,等于是指定 T 為 Object苍蔬,所以它的應(yīng)用場景不多诱建,就算真的遇到,我們也可以把它看成`? extends Object`來分析處理碟绑。
- <? extends T>
上受限通配符(Upper Bounds Wildcards)俺猿,到這里,我們可以來看看格仲,到底什么情況下應(yīng)該使用通配泛型押袍,還是之前那個例子:
public class GenericTest <T>{
private T a;
public GenericTest(T a){
this.a = a;
}
public void set(T t){
this.a = t;
}
public T get(){
return a;
}
public static int max(GenericTest<Number> a,GenericTest<Number> b,GenericTest<Number> c){
int aInt = a.get().intValue();
int bInt = b.get().intValue();
int cInt = c.get().intValue();
int max = aInt;
if (max < bInt) {
max = bInt;
}
if (max < cInt) {
max = cInt;
}
return max;
}
public static void main(String[] args){
int max = max(new GenericTest<Integer>(1),new GenericTest<Integer>(2),new GenericTest<Integer>(3));
System.out.print(max);
}
}
在之前的基礎(chǔ)上我們添加了一個判斷大小的 `max()` 方法,它接受三個泛型指定類型為 Number 的類凯肋,然后取出類里面的值進(jìn)行比較后返回最大值谊惭。仔細(xì)看下代碼,Integer 是 Number 的子類侮东,按照向上轉(zhuǎn)型原則圈盔,這樣寫是允許的,似乎并沒什么錯誤的地方悄雅。但在 IDE 上編寫時驱敲,會在倒數(shù)第四行得到一個出錯提示:
-> The method max(GenericTest<Number>, GenericTest<Number>, GenericTest<Number>)
in the type GenericTest<T> is not applicable for the arguments
(GenericTest<Integer>, GenericTest<Integer>, GenericTest<Integer>)
告訴我們 `GenericTest<Number>` 不適用于 `GenericTest<Integer>`,簡單來說就是 `GenericTest<Number>` 不能轉(zhuǎn)換成 `GenericTest<Integer>`煤伟。為什么呢癌佩?Integer 不是 Number 的子類嗎?講道理應(yīng)該是可行的啊便锨,其實(shí)道理很簡單围辙,雖然 Integer 是 Number 的子類,但是 `GenericTest<Integer>` 并不是 `GenericTest<Number>` 的子類放案,所以無法轉(zhuǎn)換姚建。
舉個更具體的例子來說,編譯器的邏輯是這樣的:學(xué)生(GenericTest)會使用筆(Number)吱殉,鋼筆(Integer)是筆的子類掸冤,編譯器是允許的厘托,但是“會使用鋼筆的學(xué)生”這個整體對象是“會使用筆的學(xué)生”這個整體對象的子類這種邏輯編譯器卻是不允許的,這時候稿湿,我們就需要借助通配符來告訴編譯器铅匹,你不要關(guān)心我和筆這個整體,你就只關(guān)心鋼筆是不是筆的子類就可以了饺藤,這樣就能進(jìn)行正常的轉(zhuǎn)換了:
public static int max(GenericTest<? extends Number> a,GenericTest<? extends Number> b,GenericTest<? extends Number> c)
結(jié)果為:
3
- <? super T>
下受限通配符(Lower Bounds Wildcards)包斑,一個上一個下,應(yīng)該很容易聯(lián)想理解涕俗,等于是告訴編譯器只要關(guān)心罗丰? 這個類是不是 T 這個類的父類就可以了。
在這里再姑,我們有一個特別需要注意的地方是上受限通配符`<? extends T>`只能從里面取東西而不能存萌抵,而下受限通配符`<? super T>`只能從里面存東西而不能取。我們可以簡單的證明一下這個原則:
GenericTest<? extends Number> genericTest = new GenericTest<Integer>(10);
genericTest.set(10);//error:set(capture#4-of ? extends Number) in the type GenericTest<capture#4-of ? extends Number> is not applicable for the arguments (int)
錯誤提示似乎和之前沒有用通配符進(jìn)行 Integer 和 Number 的轉(zhuǎn)換一樣元镀,但capture#4-of 這個是什么東西绍填?它表示的意思是編譯器捕獲到了 Integer 這個類型,但是并沒有把它設(shè)置成 Integer 而是取了個代號叫 #4-of凹联。也就相當(dāng)于 GenericTest 的類型被設(shè)置成了#4-of沐兰,而事實(shí)上并不存在這樣的類型哆档,我們自然就無法存東西進(jìn)去蔽挠。
可是編譯器為什么要這樣做呢?這是因?yàn)殡m然我們 new 出來的是一個 Integer 類型的對象瓜浸,但是持有這個對象的引用的卻是 `GenericTest<? extends Number>`澳淑,而通配符`<? extends Number>`表示的是Number的所有子類的某一子類,也就是所只要是屬于 Number 類子類的都能添加到`GenericTest<? extends T>`所持有的對象中插佛,這樣就很容易發(fā)生錯誤。
我們可以假設(shè) Number 是蘋果,它的子類可以有青蘋果紅蘋果含滴,青蘋果又可以分為小青蘋果大青蘋果虽另,這種子類的繼承是沒有下限的,所以它只有一個上限锨侯,也就是 T嫩海,所以如果我們 new 出來的是一個小青蘋果類型的對象,但由于上限是蘋果囚痴,我們也可以往這個對象里添加青蘋果對象叁怪,這就相當(dāng)于將一個父類賦值給子類了,明顯是不行的深滚,所以為了安全考慮奕谭,編譯器就直接禁止了這種賦值涣觉。但是取出來就不受限制了,因?yàn)樗幸粋€上限 T 血柳,無論取出來的是小青蘋果還是蘋果都可以向上轉(zhuǎn)型為蘋果官册。
![未命名.png](http://upload-images.jianshu.io/upload_images/506482-910053ec9b8526a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
同樣的道理分析下受限通配符 <? super T>,我們也假設(shè) T 是蘋果难捌,它的父類可以有水果攀隔,吃的,好吃的栖榨,特別好吃的昆汹,特特別好吃的……,這個父類延伸是沒有上限的婴栽,它只有一個下限 T 满粗,因此我們可以往里面任意存東西,因?yàn)槎际?T 的父類愚争,但是取就取不出來了映皆,比如我們可以存水果進(jìn)去,但取卻允許取吃的轰枝,而里面卻沒有捅彻,這樣程序就出錯了。所以編譯器就會禁止從里面取東西出來鞍陨。
這一篇比較整體的介紹了一下泛型的用法與一些限制步淹,因?yàn)橛行┎糠质亲约旱囊娊猓噪y免會有錯誤的地方诚撵,希望大家知道后能夠指出來讓我改正過來缭裆。:)下一篇將結(jié)合 Rxjava 來具體的分析一下泛型的實(shí)際應(yīng)用,估計(jì)要比較久寿烟,因?yàn)槲?Rxjava 還只看了一點(diǎn)點(diǎn)……
>->Java 編程思想 第15章 泛型
->Java 程序語言設(shè)計(jì) 第21章 泛型
->http://www.cnblogs.com/chyu/p/4630798.htm
->https://www.zhihu.com/question/20400700/answer/117464182