Java 基礎 之 泛型
一:什麼是泛型
泛型在我們的程式碼中使用非常廣泛的一部分知識,今天就係統的把泛型總結一下,並記錄自己的學習歷程。
泛型,即“引數化型別”。顧名思義,就是將型別由原來的具體的型別也定義成引數形式。然後在使用/呼叫時傳入具體的型別(實參)。這種引數型別可以用在類、介面和方法中,分別被稱為泛型類、泛型介面、泛型方法。
二:泛型的引入
首先,讓我們先來設計一個加法計算的類,可以計算int, double。


如果有一天我們需要再計算char,string的加法,那我們還得新增這樣類很多個,顯然不符合軟體設計的規範,下面我就來更新一下我們的程式碼,把所有的引數都變成object.

好了,讓我們來測試一下,我們的這個新類能不能完成,int, double,String 的加法計算

通過試驗發現,將引數抽象成object型別 後,發現只需要定義一個類就可以搞定感覺這樣挺完美的了。但是每次都需要強制轉換,這樣的程式碼設計太麻煩了!下面讓我們再升級一次我們的code,見證一下泛型的好處,不需要我們每次都強制型別轉換。
三:泛型類
泛型型別用於類的定義中,被稱為泛型類。
泛型類的定義格式:class類名稱<泛型標識>,
1:泛型標識可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型,
2:泛型的型別引數只能是類型別,不能是簡單型別,
3:在例項化泛型類時,必須指定T的具體型別
下面來定義一個泛型類,讓他滿足各種型別的加法操作


通過上面的試驗,是不是發現如果利用泛型來實現就只需要建立一個泛型類,然後例項化時傳入引數型別,不需要進行強制型別轉化就可以正確計算出結果!
但是有人就會提出來,可不可以設計成一套標準啊,我們定義這樣的一個泛型介面,讓具體的操作人員來實現這個標準,答案是當然可以!
四:泛型介面
泛型介面與泛型類的定義及使用基本相同
泛型介面的定義格式:publicinterface Generator<T>
定義一個泛型介面(定義引數的設定和獲取)

定義一個泛型類來實現這個泛型介面

但是有些人說我們可能只需要做int或String或Double的計算,不需要其他的計算,那能不能做成定製化的那?當然也是可以的,看下面的定義:

是不是發現泛型介面,更加的靈活,如果我們只需要做String的加法那就把T換成具體的資料型別String即可
總結:
如果一個類實現了一個泛型介面,那麼該類也必須是泛型的:
GenericAdd<T> implements GenericInterface<T>
如果一個類實現了一個特定型別的泛型介面,那麼該類不是泛型的:
GenericAdd implements GenericInterface<Integer>
五:泛型方法
有泛型類,泛型介面,那一定也有泛型方法。泛型類,是在例項化類的時候指明泛型的具體型別;泛型方法,是在呼叫方法的時候指明泛型的具體型別 。泛型方法指返回值和引數都用泛型表示的方法。
泛型方法的定義格式:void doTest<T t>
1:普通類中的泛型方法


通過上面的測試發現在普通類的中的定義一個泛型方法用T,V…來引數化,使用也很簡單,只需要在呼叫時傳入具體的引數型別。
2:泛型類中的泛型方法


在上面的泛型類中定義了三個泛型方法,在我們的測試中看到226行編譯出錯了,說明了第一個泛型方法只能傳入和泛型類例項化時一樣的型別,第2個和第3個泛型方法可以處理任意的型別。
在泛型類(<T>)中定義泛型方法時如果不在返回值前面指定<T>泛型型別,那這個泛型方法就只能處理與泛型類例項化時宣告的型別,如上面的第1個泛型方法 getClassInfo1
在泛型類(<T>)中聲明瞭一個使用泛型E的泛型方法,這種泛型E可以為任意型別。可以與泛型類例項化時宣告的T相同,也可以不同。如上面的第2個泛型方法 getClassInfo2
在泛型類(<T>)中聲明瞭一個泛型方法,使用泛型T,其實這個T是一種全新的型別,可以與泛型例項化時宣告的T不是同一種類型。如上面的第3個泛型方法 getClassInfo3可以處理People類
3:靜態泛型方法
在泛型類或泛型介面中,靜態函式無法使用泛型,所以,若要static方法需要使用泛型能力,必須使其成為泛型方法(靜態泛型方法)

靜態泛型方法也很簡單,就是在普通泛型方法前面加上一個static,使用方式也和普通的靜態方法一樣,直接類名.方法名就可以了!

4:泛型方法的過載
普通方法有過載,當然泛型方法也是有過載的,是因為java編譯器在編譯時會對泛型做擦除功能,所以T就會變成Object, <T extends Ape>轉成Ape, <T extends People> People。當我們這樣定義泛型方法:
public void doTest(T t)
public void doTest(T t)
public void doTest(T t)
編譯後就變成了:
publicvoid doTest(Object t)
publicvoid doTest(People t)
public void doTest(Ape t)
編譯後的三個方法就是我們很熟悉的doTest過載,下面就用code來見證一下:

使用也很簡單和普通的泛型方法一樣

s, pe, a分別是三個類型別。
六:泛型萬用字元
前面我們介紹了泛型的基本用法、型別擦除。在泛型的使用中,還有個重要的東西叫萬用字元,下面就介紹萬用字元的使用。
我們先定義三個類:Ape, People繼承自Ape,Student繼承自People
我們再定義一個泛型類

完成了基礎的class建設後,我們來講萬用字元的使用
1:<? extends T>
<? extends T>表示上界萬用字元,它表示T以及T的子類, 型別最高是T。

在上面的的code中我們定義了 <? extends People>的泛型類,理論上,他可以指向和存放自己以及所有所有屬於他的子類。但是第141,142,143卻都報錯了,編譯器卻告訴我們gp不能存放任何元素,這好像和我們前面的解釋相反:這是因為編譯器在編譯時要做型別安全檢查,當我們這樣定義gp時,就是告訴編譯器gp存的是People以及People的子類可能是Student,可能是People,也可能是其他的子類,編譯器沒辦法保證gp的下限是什麼,就不知道到底該指向什麼型別。
總結: extends不能用於引數型別限定。
2:<? super T>
<? super T>表示下界萬用字元: 表示T以及T的超類

在上面的的code中我們定義了 <? super People>的泛型類,理論上,他可以指向和存放自己以及People父類。但是我們的測試結果卻是:他可以新增People以及People的子類Student,不能新增他的父類。這好像和我們前面的解釋相反:這是因為當我們這樣定義gp1時,就是告訴編譯器gp1存的是People以及People的父類可能會放People,可能會放Ape,這樣編譯器可以確定,gp1最低的標準就是People,這樣編譯器就把下限設定為people,所以我們可以存放Student,People。
總結:super可用於引數型別限定
7: 泛型實踐
寫這麼多,當然有點乏味,下面就寫個獲取最大值和遍歷陣列的工具泛型來實踐一下:
1:建立一個泛型介面,把型別引數限制成實現了Comparable介面的類

2:再編寫一個泛型類繼承我們的泛型介面,實現獲取最大值介面,在定義一個泛型方法,完成陣列的遍歷

3:在main 函式中例項化泛型類並定義2個數組來測試獲取最大值功能,2個類陣列來測試遍歷功能
