深入理解Java泛型機(jī)制

簡(jiǎn)介

泛型的意思就是參數(shù)化類型,通過(guò)使用參數(shù)化類型創(chuàng)建的接口、類、方法,可以指定所操作的數(shù)據(jù)類型。比如:可以使用參數(shù)化類型創(chuàng)建操作不同類型的類。操作參數(shù)化類型的接口、類、方法成為泛型,比如泛型類、泛型方法。
泛型還提供缺失的類型安全性,我們知道Object是所有類的超類,在泛型前通過(guò)使用Object操作各種類型的對(duì)象,然后在進(jìn)行強(qiáng)制類型轉(zhuǎn)換。而通過(guò)使用泛型,這些類型轉(zhuǎn)換都是自動(dòng)或隱式進(jìn)行的了。因此提高了代碼重用能力,而且可以安全、容易的重用代碼。

泛型類

<pre>
public class Generic<T> {
T ob;
Generic(T o){
this.ob = o;
}
T getOb(){
return ob;
}
void showType(){
System.out.println("T type:" + ob.getClass().getName());
}
}
</pre>
<pre>
class Generic<T>
</pre>
T是類型參數(shù)名稱,使用<>括上,這個(gè)名稱是實(shí)際類型的占位符。當(dāng)創(chuàng)建一個(gè)Generic對(duì)象的時(shí)候,會(huì)傳遞一個(gè)實(shí)際類型,因?yàn)镚eneric使用了類型參數(shù),所以該類是泛型類。類中只要需要使用類型參數(shù)的地方就使用T,當(dāng)傳遞實(shí)際類型后,會(huì)自動(dòng)改變成實(shí)際類型。
比如:
T的類型就是Integer。
<pre>
Generic<Integer> gen1 = new Generic<Integer>(100);
</pre>
T的類型就是String。
<pre>
Generic<String> gen2 = new Generic<String>(“test”);
</pre>

使用泛型類

當(dāng)調(diào)用泛型構(gòu)造方法時(shí)候,仍然需要指定參數(shù)類型,因?yàn)闉闃?gòu)造函數(shù)賦值的是Generic<String>。
需要注意上述這個(gè)過(guò)程,就像Java編譯器創(chuàng)建了不同版本的Generic類,但實(shí)際編譯器并沒(méi)有那樣做,而是將所有泛型類型移除,進(jìn)行類型轉(zhuǎn)換,從而看似是創(chuàng)建了一個(gè)個(gè)Generic類版本。移除泛型的過(guò)程稱為擦除。

泛型只能使用引用類型

當(dāng)聲明泛型實(shí)例的時(shí)候,傳遞過(guò)來(lái)的類型參數(shù)必須引用類型。不能是基本類型,比如int、char等。其實(shí)可以通過(guò)類型封裝器封裝基本類型,所以這個(gè)限制并不嚴(yán)格。
<pre>
Generic<int> gen3 = new Generic<int>();
</pre>

基于不同類型的泛型類是不同的,比如Generic<Integer> gen1和Generic<String> gen2雖然都是Generic<T>類型,但是它們是不同的類型引用,所以gen1 != gen2。這個(gè)就是泛型添加類型安全以及防止錯(cuò)誤的一部分。

泛型類型安全的原理

上面我們說(shuō)過(guò),其實(shí)泛型的實(shí)現(xiàn)完全可以通過(guò)使用Object類型替換,將Genneric中所有T轉(zhuǎn)換成Object類型,然后在使用時(shí)候通過(guò)強(qiáng)制類型轉(zhuǎn)換獲取值。但是這有許多風(fēng)險(xiǎn)的,比如手動(dòng)輸入強(qiáng)制類型轉(zhuǎn)換、進(jìn)行類型檢查。而實(shí)用泛型它會(huì)將這些操作將是隱式完成的,泛型能夠保證自動(dòng)確保類型安全。可以將運(yùn)行時(shí)錯(cuò)誤轉(zhuǎn)換成編譯時(shí)錯(cuò)誤,比如如果實(shí)用Object替代泛型,對(duì)于之前Generic<Integer> gen1 和Generic<String> gen2,將gen1 = gen2這樣在泛型中直接編譯錯(cuò)誤,如果使用Object替代,則不會(huì)產(chǎn)生編譯錯(cuò)誤,因?yàn)樗鼈儽旧矶际荊eneric類型,但是在執(zhí)行相關(guān)代碼時(shí)候會(huì)出錯(cuò),比如getOb()將String類型直接賦值給int類型。

多個(gè)類型參數(shù)的泛型類

當(dāng)需要聲明多個(gè)參數(shù)類型時(shí),只需要使用逗號(hào)分隔參數(shù)列表即可。
<pre>
public class Generic<T,V> {
T ob1;
V ob2;
Generic(T ob1,V ob2){
this.ob1 = ob1;
this.ob2 = ob2;
}
T getOb1(){
return ob1;
}
V getOb2(){
return ob2;
}
void showType(){
System.out.println("T type:" + ob1.getClass().getName());
System.out.println("V type:" + ob2.getClass().getName());
}
}
</pre>
這樣在創(chuàng)建Generic實(shí)例時(shí)候,需要分別給出參數(shù)類型。
<pre>
Generic<String,Integer> generic = new Generic<String,Integer>(“test”,123);
</pre>
泛型類定語(yǔ)法:
<pre>
class class-name<type-param-list>{
//….
}
</pre>
泛型類引用語(yǔ)法:
<pre>
class-name<type-param-list> var-name = new class-name<type-param-list>(con-arg-list);
</pre>

有界類型(bounded type)

前面討論的泛型,可以被任意類型替換。對(duì)于絕大多數(shù)情況是沒(méi)問(wèn)題的,但是一些特殊場(chǎng)景需要對(duì)傳遞的類型進(jìn)行限制,比如一個(gè)泛型類只能是數(shù)字,不希望使用其它類型。我們知道無(wú)論Integer還是Double都是Number的子類,所以可以限制只有Number及其子類可以使用,定義的泛型類的時(shí)候,在泛型類中可以使用Number中定義的方法(否則無(wú)法使用,比如使用Number類中的doubleValue(),如果直接使用會(huì)無(wú)法通過(guò)編譯,因?yàn)門(mén)泛型,并不知道你這個(gè)參數(shù)類型是什么)。

<pre>
public class Generic<T extends Number> {
T[] array;
Generic(T[] array){
this.array = array;
}
double average(){
double sum = 0;
for(int i=0;i<array.length;i++){
sum += array[i].doubleValue();
}
return sum / array.length;
}
}

Generic<T extends Number>
</pre>

這樣T只能被Number及其子類代替,這時(shí)候java編譯器也知道T類型的對(duì)象都可以調(diào)用dobuleValue()方法,因?yàn)檫@個(gè)方法是Number中定義的。
除了可以使用類作為邊界,也可以使用接口作為邊界,使用方式與上面相同。同時(shí)也可以同時(shí)使用一個(gè)類和一個(gè)接口或多個(gè)接口邊界,對(duì)于這種情況,需要先指定類類型。如果指定接口類型,那么實(shí)現(xiàn)了這個(gè)接口的類型參數(shù)是合法的。
<pre>
class class-name<T extends MyClass & MyInterface>
</pre>

使用通配符參數(shù)

我們繼續(xù)擴(kuò)展上面這個(gè)類,當(dāng)需要一個(gè)sameAvg()方法用來(lái)比較兩個(gè)對(duì)象的average()接口是否相同,這個(gè)sameAvg()接口怎么寫(xiě)?
第一種方式:
<pre>
boolean sameAvg(Generic<T> ob){
if(average() == ob.average())
return true;
return false;
}
</pre>
這種方式有一個(gè)弊端,就是Generic<Integer>只能和Generic<Integer>比較(上面說(shuō)了),而我們比較相同平均數(shù)并care類型。這時(shí)我們可以使用通配符“?”來(lái)解決。
第二種方式:
<pre>
boolean sameAvg(Generic<?> ob){
//...
}
</pre>

使用通配符需要理解一點(diǎn),它本身不會(huì)影響創(chuàng)建什么類型的Generic對(duì)象,通配符只是簡(jiǎn)單匹配所有有效的(有界類型下的)Generic對(duì)象。

有界通配符

使用有界通配符,可以為參數(shù)類型指定上界和下界,從而能夠限制方法能夠操作的對(duì)象類型。最常用的是指定有界通配符上界,使用extends子句創(chuàng)建。

<pre>
<? extends superclass>
</pre>
這樣直有superclass類及其子類可以使用。也可以指定下界:
<pre>
<? super subclass>
</pre>

這樣subclass的超類是可接受的參數(shù)類型。
有界通配符的應(yīng)用場(chǎng)景一般是操作類層次的泛型(C 繼承 B,B繼承A),控制層次類型。

創(chuàng)建泛型方法

之前討論泛型類中的泛型方法都是使用創(chuàng)建實(shí)例傳遞過(guò)來(lái)的類型,其實(shí)方法可以本身使用一個(gè)或多個(gè)類型參數(shù)的泛型方法。并且,可以在非泛型類中創(chuàng)建泛型方法。
<pre>
class GenericDemo {
<T extends Comparator<T>, V extends T> boolean isIn(T x, V[] y) {
for (int i = 0; i < y.length; i++) {
if (x.equals(y[i]))
return true;
}
return false;
}
}
</pre>
<pre>
<T extends Comparator<T>, V extends T> boolean isIn(T x, V[] y)
</pre>
泛型參數(shù)在返回類型之前,T擴(kuò)展了類型Comparator<T>,所以只有實(shí)現(xiàn)了Comparator<T>接口的類才可以使用。同時(shí)V設(shè)置了T為上界,這樣V必須是T或者其子類。通過(guò)強(qiáng)制參數(shù),達(dá)到相互兼容。
調(diào)用isIn()時(shí)候一般可以直接使用,不需要指定類型參數(shù),類型推斷就可以自動(dòng)完成。當(dāng)然你也可以指定類型:
<pre>
<Integer,Integer>isIn(3,nums);
</pre>
泛型方法語(yǔ)法:
<pre>
<type-param-list> ret-type meth-name(param-list){
//..
}
</pre>
也可以為構(gòu)造方法泛型化,即便類不是泛型類,但是構(gòu)造方法是。所以在構(gòu)造該實(shí)例時(shí)候需要根據(jù)泛型類型給出。
<pre>
<T extends Number> Generic(T a){
//..
}
</pre>

泛型接口

泛型接口與定義泛型類是類似的
<pre>
interface MyInterface<T extends Comparable<T>>{
//...
}
</pre>
當(dāng)類實(shí)現(xiàn)接口時(shí)候,因?yàn)榻涌谥付ń缦?,所以?shí)現(xiàn)類也需要指定相同的界限。并且接口一旦建立這個(gè)界限,那么在實(shí)現(xiàn)他的時(shí)候就不需要在指定了。
<pre>
class MyClass<T extends Compareable<T>> implements MyInterface<T>{
//...
}
</pre>
如果類實(shí)現(xiàn)了具體類型的泛型接口,實(shí)現(xiàn)類可以不指出泛型類型。
<pre>
class MyClass implements MyInterface<Integer>{
//...
}
</pre>
使用泛型接口,可以針對(duì)不同類型數(shù)據(jù)進(jìn)行實(shí)現(xiàn);使用泛型接口也為實(shí)現(xiàn)類設(shè)置了類型限制條件。
定義泛型接口語(yǔ)法:
<pre>
interface interface-name<type-param-list>{
//...
}
</pre>
實(shí)現(xiàn)泛型接口
<pre>
class class-name<type-params-list> implements interface-name<type-arg-list>{
//...
}
</pre>

遺留代碼中的原始類型

泛型是在JDK 5之后提供的,在JDK 5之前是不支持的泛型的。所以這些遺留代碼即需要保留功能,又要和泛型兼容??梢允褂没旌暇幋a,還比如上面的例子。Generic<T> 類是一個(gè)泛型類,我們可以使用原始類型(不指定泛型類型),來(lái)創(chuàng)建Generic類。
<pre>
Generic gen1 = new Generic(new Double(9.13));
double gen2 = (Double)gen1.getOb();
</pre>
java是支持這種原始類型,然后通過(guò)強(qiáng)制類型轉(zhuǎn)換使用的。但是正如我們上面說(shuō)的,這就繞過(guò)了泛型的類型檢查,它是類型不安全的,有可能導(dǎo)致運(yùn)行時(shí)異常(RunTime Exception)。

泛型類層次

泛型類也可以是層次的一部分,就像非泛型類那樣。泛型類可以作為超類或子類。泛型和非泛型的區(qū)別在于,泛型類層次中的所有子類會(huì)將類型向上傳遞給超類。
<pre>
class Gen<T>{
T ob;
Gen(T ob){
this.ob = ob;
}
T genOb(){
return ob;
}
}
class Gen2<T> extends Gen<T>{
Gen2(T o){
super(o);//向上傳遞
}
}
</pre>
<pre>
Gen2<Integer> gen = new Gen2<Integer>();
</pre>
創(chuàng)建Gen2傳入Integer類型,Integer類型也會(huì)傳入超類Gen中。子類可以根據(jù)自己的需求,任意添加參數(shù)類型。
<pre>
class Gen2<T,V> extends Gen<T>{
Gen2(T a,V b){
super(a);//一定要有
}
}
</pre>
超類也可以不是泛型,子類在繼承的時(shí)候,就不需要有特殊的條件了。
<pre>
class Gen{
Gen(int a){
}
}
class Gen2<T,V> extends Gen{
Gen2(T a,int b){
super(b);
}
Gen2(T a,V b,int c){
super(c);
}
}
</pre>
需要注意的:

  • 泛型類型強(qiáng)制類型轉(zhuǎn)換,需要兩個(gè)泛型實(shí)例的類型相互兼容并且它們的類型參數(shù)也相同。
  • 可以向重寫(xiě)其它方法那樣重寫(xiě)泛型的方法。
  • 從JDK 7起泛型可以使用類型推斷在創(chuàng)建實(shí)例時(shí)候省略類型,因?yàn)樵趨?shù)聲明的時(shí)候已經(jīng)指定過(guò)一次了,所以可以根據(jù)聲明的變量進(jìn)行類型推斷。
    List<String,Integer> list = new ArrayList<>();

擦除

泛型為了兼容以前的代碼(JDK 5之前的),使用了擦除實(shí)現(xiàn)泛型。具體就是,當(dāng)編譯java代碼的時(shí)候,所有泛型信息被移除(擦除)。會(huì)使用它們的界定類型替換,如果沒(méi)有界定類型,會(huì)使用Object,然后進(jìn)行適當(dāng)?shù)念愋娃D(zhuǎn)換。

模糊性錯(cuò)誤

泛型引入后,也增加了一種新類型錯(cuò)誤-模糊性錯(cuò)誤的可能,需要進(jìn)行防范。當(dāng)擦除導(dǎo)致兩個(gè)看起來(lái)不同的泛型聲明,在擦除之后可能變成相同類型,從而導(dǎo)致沖突。
<pre>
class Gen<T,V>{
T ob1;
V ob2;
void setOb(T ob){
this.ob1 = ob;
}
void setOb(V ob){
this.ob2 = ob;
}
}
</pre>
這種是無(wú)法編譯的,因?yàn)楫?dāng)擦除后可能會(huì)導(dǎo)致類型相同,這樣的方法重載是不對(duì)的。
<pre>
Gen<String,String> gen = new Gen<String,String>();
</pre>
這樣T和V都是String類型,明顯代碼是不對(duì)的。
可以通過(guò)指定一個(gè)類型邊界,比如:
<pre>
class Test1{
public static void main(String[] args){
//沒(méi)問(wèn)題
Gen<String,Integer> gen = new Gen<String, Integer>();
gen.setOb(1);
//這樣在調(diào)用setOb的時(shí)候也會(huì)編譯失敗,因?yàn)槎紴镮nteger類型,方法重載錯(cuò)誤
Gen<Integer,Integer> gen1 = new Gen<Integer, Integer>();
gen1.setOb(1);
}
}
</pre>
所以在解決這種模糊錯(cuò)誤時(shí)候,最好使用獨(dú)立的方法名,而不是去重載。

使用泛型的限制

  • 不能實(shí)例化類型參數(shù),因?yàn)榫幾g器不知道創(chuàng)建哪種類型,T只是類型占位符。
    <pre>
    class Gen<T>{
    T ob;
    Gen(){
    ob = new T();
    }
    }
    </pre>
  • 靜態(tài)成員不能使用類中聲明的類型參數(shù)。
    <pre>
    class Gen<T>{
    //錯(cuò)誤的,不能聲明靜態(tài)成員
    static T ob;
    //錯(cuò)誤的,靜態(tài)方法不能使用參數(shù)類型T
    static T getGen(){
    return ob;
    }
    //正確的,靜態(tài)方法不是參數(shù)類型
    static void printXXX(){
    System.out.println();
    }
    }
    </pre>
  • 不能實(shí)例化類型參數(shù)數(shù)組
    <pre>
    //沒(méi)問(wèn)題
    T[] vals;
    //不能實(shí)例化類型參數(shù)數(shù)組
    vals = new T[10];
    </pre>
  • 不能創(chuàng)建特性類型的泛型應(yīng)用數(shù)組
    <pre>
    //這是不允許的
    Gen<Integer> gen = new Gen<Integer>[10];
    但是可以使用通配符,并且比使用原始類型好,因?yàn)檫M(jìn)行了類型檢查。
    Gen<?> gen = new Gen<?>[10];
    </pre>
  • 泛型類不能擴(kuò)展Throwable,這就意味著不嗯滾創(chuàng)建泛型異常類。

關(guān)注我

歡迎關(guān)注我的公眾號(hào),會(huì)定期推送優(yōu)質(zhì)技術(shù)文章,讓我們一起進(jìn)步、一起成長(zhǎng)!
公眾號(hào)搜索:data_tc
或直接掃碼:??


歡迎關(guān)注我
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在之前的文章中分析過(guò)了多態(tài),可以知道多態(tài)本身是一種泛化機(jī)制,它通過(guò)基類或者接口來(lái)設(shè)計(jì),使程序擁有一定的靈活性,但是...
    _小二_閱讀 765評(píng)論 0 0
  • object 變量可指向任何類的實(shí)例,這讓你能夠創(chuàng)建可對(duì)任何數(shù)據(jù)類型進(jìn)程處理的類。然而,這種方法存在幾個(gè)嚴(yán)重的問(wèn)題...
    CarlDonitz閱讀 1,030評(píng)論 0 5
  • Java泛型總結(jié)# 泛型是什么## 從本質(zhì)上講,泛型就是參數(shù)化類型。泛型十分重要,使用該特性可以創(chuàng)建類、接口以及方...
    kylinxiang閱讀 979評(píng)論 0 1
  • 今年春節(jié)假期參與了鄭州十點(diǎn)讀書(shū)會(huì)組織的共讀活動(dòng),我讀的是蔣勛老師的《品味四講》,一共用了十天的時(shí)間才斷斷續(xù)續(xù)的看完...
    正齊讀道閱讀 707評(píng)論 3 0
  • 下午戶外活動(dòng)是夾球跳,龍龍經(jīng)過(guò)前幾周玩耍的經(jīng)驗(yàn)龍龍已經(jīng)掌握夾球的秘訣啦,剛拿到球就能輕松的把球給夾起來(lái),并夾著球跳...
    a81c671c0ae2閱讀 314評(píng)論 0 0

友情鏈接更多精彩內(nèi)容