從 Rxjava 看 Java 泛型(一)

最近在看 Rxjava 與 Retrofit ,發現它們都大量的使用到了泛型這個概念,之前我對泛型是有一點了解的,但是這點了解明顯在學習它們的時候不夠用,于是便花了點時間重新整體的學習了一遍泛型。

什么是泛型?###

泛型(generic)是指參數化類型的能力。

泛,廣泛,按照字面意思可以簡單的理解為它的含義是廣泛的類型,事實也是如此,當我們為接口、方法或者類定義為泛型時,就說明它們可以接受的類型是廣泛的,可以是 String ,int,date 等等。我們也可以指定泛型的具體類型,這樣,當傳入的對象類型與我們指定的類型不相符時編譯器就會報錯,能讓代碼更為健壯。

一個簡單的例子:

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 數組中加入了兩個 Sting 對象和 一個 int 對象,但在取出來時將它們全部強轉成 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 ,當你為它添加了除 String 外的類型對象時,IDE 在你寫代碼的時候就會提示你出錯,而不會等到程序運行時才報錯。

通常情況下,當我們不指定 ArrayList 泛型的具體類型時,可以把它的泛型類型視為 Object,即所有對象。也就是說,ArrayList 默認是泛型類型,即 ArrayList<E> ,這樣我們就可以根據實際情況來指定 ArrayList 的具體類型。

從這里我們可以看出,當我們編寫的代碼實現了泛型后,這種代碼就會變得更為通用,因為它可以在具體使用中根據實際要求來定義參數類型,這也正是泛型的定義:參數化類型。

泛型的使用###

  • 泛型類:

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 的類型設置為 <T>,即表示泛型,我們也可以用 E,或 V 來表示。可以看到,在主函數中,我們將 T 的類型分別指定為 String 和 Integer,然后再打印出來,結果如下:

hello,world
2016

從這我們可以得到這樣一條通用語句:
`GenericTest<Xxx> xxx = new GenericTest<Xxx>(new Xxx());`
即 GenericTest 可以接受任何類型的對象。在這里,我們如果將 T 改為 Object 的話,GenericTest 同樣能接受所有類型,但是如果假設 GenericTest 的作用是個集合呢?如 ArrayList?相信區別大家一眼便能看出來。所以,將類的類型指定為泛型而不是 Object 明顯能更適用于多場景。

這里,我們需要注意的一點是,雖然在編譯時 GenericTest<String> 與 GenericTest<Integer> 是兩種類型,但實際上運行時只有一個 GenericTest 類會被加載到JVM 中,如下所示:

System.out.println(wordString +"->" + string.getClass().getName());
System.out.println(year + "->" + integer.getClass().getName());
運行結果為:
hello,world->GenericTest
2016->GenericTest

為什么呢?因為真正運行時泛型是不存在的,也就是說泛型只會在編譯時存在,在運行時會被擦除。所以, 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>,也就是說,泛型的意義在于能讓你在編寫的代碼在編譯時就檢查它的類型安全,并且它的類型轉換都是自動和隱式的,而用 Object 的類型轉換則是顯式轉換,并且當真正的參數錯誤時不會提出警告,如第一個例子。當然,泛型的真正意義遠不如此,還需要我們繼續去深入學習。

- 泛型方法

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 泛型類一樣的效果,它和泛型類的區別在于泛型類型`<T>`要放在方法返回類型之前,如:`<T> void print(T t)`,而泛型類要放在類名后面,如:`GenericTest<T>`。既然它們效果都是一樣的,那具體使用時我們應該用泛型類還是方法呢?
> 無論何時,只要你能做到,你就應該盡量的使用泛型方法。

  為什么呢?因為泛型方法讓問題變得更為具體,而不是一個整體的對象。另外,對于靜態方法來說,它無法訪問泛型類的類型參數,所以,它想擁有泛型的能力,就必須定義為泛型方法。

- 泛型接口

public interface Comparable<T>{
public int compareTo(T o);
}

這個接口是 JDK 1.5之后自帶的一個比較接口,我們可以看出,它的定義方式和泛型類的定義相差無幾,泛型類型`<T>`也是放在接口名后面,使用也基本沒什么差別。

###通配泛型###

首先,什么是通配泛型?為什么要使用到通配泛型呢?不急,我們先介紹一下通配泛型,通配泛型的類型有三種形式:`<?>`、`<? extens T>`、`<? super T>`,我們分別來講解一下它們的具體用法與意義。

- <?>

非受限通配符(unbounded wildcard),它與第二種方式其實可以歸為一類,因為它的作用相當于是`? extends Object`,等于是指定 T 為 Object,所以它的應用場景不多,就算真的遇到,我們也可以把它看成`? extends Object`來分析處理。

- <? extends T>

上受限通配符(Upper Bounds Wildcards),到這里,我們可以來看看,到底什么情況下應該使用通配泛型,還是之前那個例子:

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);
}

}

在之前的基礎上我們添加了一個判斷大小的  `max()` 方法,它接受三個泛型指定類型為 Number 的類,然后取出類里面的值進行比較后返回最大值。仔細看下代碼,Integer 是 Number 的子類,按照向上轉型原則,這樣寫是允許的,似乎并沒什么錯誤的地方。但在 IDE 上編寫時,會在倒數第四行得到一個出錯提示:

-> 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>` 不能轉換成 `GenericTest<Integer>`。為什么呢?Integer 不是 Number 的子類嗎?講道理應該是可行的啊,其實道理很簡單,雖然 Integer 是 Number 的子類,但是 `GenericTest<Integer>` 并不是 `GenericTest<Number>` 的子類,所以無法轉換。

舉個更具體的例子來說,編譯器的邏輯是這樣的:學生(GenericTest)會使用筆(Number),鋼筆(Integer)是筆的子類,編譯器是允許的,但是“會使用鋼筆的學生”這個整體對象是“會使用筆的學生”這個整體對象的子類這種邏輯編譯器卻是不允許的,這時候,我們就需要借助通配符來告訴編譯器,你不要關心我和筆這個整體,你就只關心鋼筆是不是筆的子類就可以了,這樣就能進行正常的轉換了:

public static int max(GenericTest<? extends Number> a,GenericTest<? extends Number> b,GenericTest<? extends Number> c)
結果為:
3

- <? super T>

下受限通配符(Lower Bounds Wildcards),一個上一個下,應該很容易聯想理解,等于是告訴編譯器只要關心? 這個類是不是 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)

錯誤提示似乎和之前沒有用通配符進行 Integer 和 Number 的轉換一樣,但capture#4-of 這個是什么東西?它表示的意思是編譯器捕獲到了 Integer 這個類型,但是并沒有把它設置成 Integer 而是取了個代號叫 #4-of。也就相當于 GenericTest 的類型被設置成了#4-of,而事實上并不存在這樣的類型,我們自然就無法存東西進去。

可是編譯器為什么要這樣做呢?這是因為雖然我們 new 出來的是一個 Integer 類型的對象,但是持有這個對象的引用的卻是 `GenericTest<? extends Number>`,而通配符`<? extends Number>`表示的是Number的所有子類的某一子類,也就是所只要是屬于 Number 類子類的都能添加到`GenericTest<? extends T>`所持有的對象中,這樣就很容易發生錯誤。

我們可以假設 Number 是蘋果,它的子類可以有青蘋果紅蘋果,青蘋果又可以分為小青蘋果大青蘋果,這種子類的繼承是沒有下限的,所以它只有一個上限,也就是 T,所以如果我們 new 出來的是一個小青蘋果類型的對象,但由于上限是蘋果,我們也可以往這個對象里添加青蘋果對象,這就相當于將一個父類賦值給子類了,明顯是不行的,所以為了安全考慮,編譯器就直接禁止了這種賦值。但是取出來就不受限制了,因為它有一個上限 T ,無論取出來的是小青蘋果還是蘋果都可以向上轉型為蘋果。

![未命名.png](http://upload-images.jianshu.io/upload_images/506482-910053ec9b8526a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


同樣的道理分析下受限通配符 <? super T>,我們也假設 T 是蘋果,它的父類可以有水果,吃的,好吃的,特別好吃的,特特別好吃的……,這個父類延伸是沒有上限的,它只有一個下限 T ,因此我們可以往里面任意存東西,因為都是 T 的父類,但是取就取不出來了,比如我們可以存水果進去,但取卻允許取吃的,而里面卻沒有,這樣程序就出錯了。所以編譯器就會禁止從里面取東西出來。

這一篇比較整體的介紹了一下泛型的用法與一些限制,因為有些部分是自己的見解,所以難免會有錯誤的地方,希望大家知道后能夠指出來讓我改正過來。:)下一篇將結合 Rxjava 來具體的分析一下泛型的實際應用,估計要比較久,因為我 Rxjava 還只看了一點點……

>->Java 編程思想 第15章 泛型
->Java 程序語言設計 第21章 泛型
->http://www.cnblogs.com/chyu/p/4630798.htm
->https://www.zhihu.com/question/20400700/answer/117464182
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 開發人員在使用泛型的時候,很容易根據自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數,那么如果嘗試...
    時待吾閱讀 1,072評論 0 3
  • 在之前的文章中分析過了多態,可以知道多態本身是一種泛化機制,它通過基類或者接口來設計,使程序擁有一定的靈活性,但是...
    _小二_閱讀 696評論 0 0
  • 第8章 泛型 通常情況的類和函數,我們只需要使用具體的類型即可:要么是基本類型,要么是自定義的類。但是在集合類的場...
    光劍書架上的書閱讀 2,156評論 6 10
  • 得分后衛已經不再是科比麥迪時代能通過各種方式得分的球隊當家,就現代聯盟對得分后衛的“定義”來看,得分后衛更傾向于球...
    zoneball閱讀 268評論 0 1
  • 九月份,校友合唱團要演出一場民國時期的學堂樂歌。其中,有一首是霞霞的太爺爺于1918年創辦的敬德國民學校(位于寧波...
    林宏海閱讀 544評論 0 0