版權(quán)申明】非商業(yè)目的附文章鏈接可自由轉(zhuǎn)載
博文地址:http://www.reibang.com/p/fb6f33cac3bf
出自:shusheng007
概述
什么是泛型枉证?為什么需要泛型贮尉?如何使用?是什么原理躺坟?如何改進(jìn)? 這基本上就是我們學(xué)習(xí)一項(xiàng)技術(shù)的正確套路和二,本文將按照以上順序展開(kāi)幅虑,由于水平有限,肯定會(huì)有不足之處盒刚,請(qǐng)多包含和指教腺劣。
什么是泛型
泛型的本質(zhì)是參數(shù)化類型,即給類型指定一個(gè)參數(shù),然后在使用時(shí)再指定此參數(shù)具體的值,那樣這個(gè)類型就可以在使用時(shí)決定了郁惜。這種參數(shù)類型可以用在類、接口和方法中趾断,分別被稱為泛型類、泛型接口吓懈、泛型方法歼冰。
為什么需要泛型
Java中引入泛型最主要的目的是將類型檢查工作提前到編譯時(shí)期靡狞,將類型強(qiáng)轉(zhuǎn)(cast)工作交給編譯器耻警,從而讓你在編譯時(shí)期就獲得類型轉(zhuǎn)換異常以及去掉源碼中的類型強(qiáng)轉(zhuǎn)代碼。例如
沒(méi)有泛型前:
private static void genericTest() {
List arrayList = new ArrayList();
arrayList.add("總有刁民想害朕");
arrayList.add(7);
for (int i = 0; i < arrayList.size(); i++) {
Object item = arrayList.get(i);
if (item instanceof String) {
String str = (String) item;
System.out.println("泛型測(cè)試 item = " + str);
}else if (item instanceof Integer)
{
Integer inte = (Integer) item;
System.out.println("泛型測(cè)試 item = " + inte);
}
}
}
如上代碼所示甸怕,在沒(méi)有泛型之前類型的檢查和類型的強(qiáng)轉(zhuǎn)都必須由我們程序員自己負(fù)責(zé)甘穿,一旦我們犯了錯(cuò)(誰(shuí)還能不犯錯(cuò)?)梢杭,就是一個(gè)運(yùn)行時(shí)崩潰等著我們温兼。那時(shí)候我們就會(huì)抱怨了:你媽的狗逼編譯器,毛也檢查不出來(lái)武契,我把一個(gè)Integer
類型的對(duì)象強(qiáng)行轉(zhuǎn)換成String
類型你在編譯的時(shí)候也不告訴我募判,害的老子程序運(yùn)行時(shí)崩潰了,這個(gè)月獎(jiǎng)金沒(méi)了咒唆!
有了泛型后:
private static void genericTest2() {
List<String> arrayList = new ArrayList<>();
arrayList.add("總有刁民想害朕");
arrayList.add(7); //..(參數(shù)不匹配:int 無(wú)法轉(zhuǎn)換為String)
...
}
如上代碼届垫,編譯器在編譯時(shí)期即可完成類型檢查工作,并提出錯(cuò)誤(其實(shí)IDE在代碼編輯過(guò)程中已經(jīng)報(bào)紅了)全释。這次該編譯器發(fā)飆了:你丫是不是傻装处,這是一個(gè)只能放入String
類型的列表,你放Int
類型是幾個(gè)意思浸船?
泛型作用的對(duì)象
泛型有三種使用方式妄迁,分別為:泛型類寝蹈、泛型接口和泛型方法。
泛型類
在類的申明時(shí)指定參數(shù)登淘,即構(gòu)成了泛型類箫老,例如下面代碼中就指定T
為類型參數(shù),那么在這個(gè)類里面就可以使用這個(gè)類型了形帮。例如申明T
類型的變量name
槽惫,申明T
類型的形參param
等操作。
public class Generic<T> {
public T name;
public Generic(T param){
name=param;
}
public T m(){
return name;
}
}
那么在使用類時(shí)就可以傳入相應(yīng)的類型辩撑,構(gòu)建不同類型的實(shí)例界斜,如下面代碼分別傳入了String
,Integer
,Boolean
3個(gè)類型:
private static void genericClass()
{
Generic<String> str=new Generic<>("總有刁民想害朕");
Generic<Integer> integer=new Generic<>(110);
Generic<Boolean> b=new Generic<>(true);
System.out.println("傳入類型:"+str.name+" "+integer.name+" "+b.name);
}
輸出結(jié)果為:傳入類型:總有刁民想害朕 110 true
如果沒(méi)有泛型,我們想要達(dá)到上面的效果需要定義三個(gè)類合冀,或者一個(gè)包含三個(gè)構(gòu)造函數(shù)各薇,三個(gè)取值方法的類。
泛型接口
泛型接口與泛型類的定義基本一致
public interface Generator<T> {
public T produce();
}
泛型方法
這個(gè)相對(duì)來(lái)說(shuō)就比較復(fù)雜君躺,當(dāng)我首次接觸時(shí)也是一臉懵逼峭判,抓住特點(diǎn)后也就沒(méi)有那么難了。
public class Generic<T> {
public T name;
public Generic(){}
public Generic(T param){
name=param;
}
public T m(){
return name;
}
public <E> void m1(E e){ }
public <T> T m2(T e){ }
}
重點(diǎn)看public <E> void m1(E e){ }
這就是一個(gè)泛型方法棕叫,判斷一個(gè)方法是否是泛型方法關(guān)鍵看方法返回值前面有沒(méi)有使用<>
標(biāo)記的類型林螃,有就是,沒(méi)有就不是俺泣。這個(gè)<>
里面的類型參數(shù)就相當(dāng)于為這個(gè)方法聲明了一個(gè)類型疗认,這個(gè)類型可以在此方法的作用塊內(nèi)自由使用。
上面代碼中伏钠,m()
方法不是泛型方法横漏,m1()
與m2()
都是。值得注意的是m2()
方法中聲明的類型T
與類申明里面的那個(gè)參數(shù)T
不是一個(gè)熟掂,也可以說(shuō)方法中的T
隱藏了類型中的T
缎浇。下面代碼中類里面的T
傳入的是String
類型,而方法中的T
傳入的是Integer
類型赴肚。
Generic<String> str=new Generic<>("總有刁民想害朕");
str.m2(123);
泛型的使用方法
如何繼承一個(gè)泛型類
如果不傳入具體的類型素跺,則子類也需要指定類型參數(shù),代碼如下:
class Son<T> extends Generic<T>{}
如果傳入具體參數(shù)誉券,則子類不需要指定類型參數(shù)
class Son extends Generic<String>{}
如何實(shí)現(xiàn)一個(gè)泛型接口
class ImageGenerator<T> implements Generator<T>{
@Override
public T produce() {
return null;
}
}
如何調(diào)用一個(gè)泛型方法
和調(diào)用普通方法一致指厌,不論是實(shí)例方法還是靜態(tài)方法。
通配符横朋?
仑乌?代表任意類型,例如有如下函數(shù):
public void m3(List<?>list){
for (Object o : list) {
System.out.println(o);
}
}
其參數(shù)類型是?
晰甚,那么我們調(diào)用的時(shí)候就可以傳入任意類型的List
,如下
str.m3(Arrays.asList(1,2,3));
str.m3(Arrays.asList("總有刁民","想害","朕"));
但是說(shuō)實(shí)話衙传,單獨(dú)一個(gè)?
意義不大厕九,因?yàn)榇蠹铱梢钥吹奖痛罚瑥募现蝎@取到的對(duì)象的類型是Object
類型的,也就只有那幾個(gè)默認(rèn)方法可調(diào)用扁远,幾乎沒(méi)什么用俊鱼。如果你想要使用傳入的類型那就需要強(qiáng)制類型轉(zhuǎn)換,這是我們接受不了的畅买,不然使用泛型干毛并闲。其真正強(qiáng)大之處是可以通過(guò)設(shè)置其上下限達(dá)到類型的靈活使用,且看下面分解重點(diǎn)內(nèi)容谷羞。
通配符上界
通配符上界使用<? extends T>
的格式帝火,意思是需要一個(gè)T類型或者T類型的子類,一般T類型都是一個(gè)具體的類型湃缎,例如下面的代碼犀填。
public void printIntValue(List<? extends Number> list) {
for (Number number : list) {
System.out.print(number.intValue()+" ");
}
}
這個(gè)意義就非凡了,無(wú)論傳入的是何種類型的集合嗓违,我們都可以使用其父類的方法統(tǒng)一處理九巡。
通配符下界
通配符下界使用<? super T>
的格式,意思是需要一個(gè)T類型或者T類型的父類蹂季,一般T類型都是一個(gè)具體的類型冕广,例如下面的代碼。
public void fillNumberList(List<? super Number> list) {
list.add(new Integer(0));
list.add(new Float(1.0));
}
至于什么時(shí)候使用通配符上界乏盐,什么時(shí)候使用下界佳窑,在《Effective Java》中有很好的指導(dǎo)意見(jiàn):遵循PECS原則制恍,即producer-extends,consumer-super. 換句話說(shuō)父能,如果參數(shù)化類型表示一個(gè)生產(chǎn)者,就使用 <? extends T>
净神;如果參數(shù)化類型表示一個(gè)消費(fèi)者何吝,就使用<? super T>
。
泛型在靜態(tài)方法中的問(wèn)題
泛型類中的靜態(tài)方法和靜態(tài)變量不可以使用泛型類所聲明的泛型類型參數(shù)鹃唯,例如下面的代碼編譯失敗
public class Test<T> {
public static T one; //編譯錯(cuò)誤
public static T show(T one){ //編譯錯(cuò)誤
return null;
}
}
因?yàn)殪o態(tài)方法和靜態(tài)變量屬于類所有爱榕,而泛型類中的泛型參數(shù)的實(shí)例化是在創(chuàng)建泛型類型對(duì)象時(shí)指定的,所以如果不創(chuàng)建對(duì)象坡慌,根本無(wú)法確定參數(shù)類型黔酥。但是靜態(tài)泛型方法是可以使用的,我們前面說(shuō)過(guò),泛型方法里面的那個(gè)類型和泛型類那個(gè)類型完全是兩回事跪者。
public static <T>T show(T one){
return null;
}
Java泛型原理解析
為什么人們會(huì)說(shuō)Java的泛型是偽泛型呢棵帽,就是因?yàn)镴ava在編譯時(shí)擦除了所有的泛型信息,所以Java根本不會(huì)產(chǎn)生新的類型到字節(jié)碼或者機(jī)器碼中渣玲,所有的泛型類型最終都將是一種原始類型逗概,那樣在Java運(yùn)行時(shí)根本就獲取不到泛型信息。
擦除
Java編譯器編譯泛型的步驟:
1.檢查泛型的類型 忘衍,獲得目標(biāo)類型
2.擦除類型變量逾苫,并替換為限定類型(T為無(wú)限定的類型變量,用Object替換)
3.調(diào)用相關(guān)函數(shù)枚钓,并將結(jié)果強(qiáng)制轉(zhuǎn)換為目標(biāo)類型铅搓。
ArrayList<String> arrayString=new ArrayList<String>();
ArrayList<Integer> arrayInteger=new ArrayList<Integer>();
System.out.println(arrayString.getClass()==arrayInteger.getClass());
上面代碼輸入結(jié)果為 true,可見(jiàn)通過(guò)運(yùn)行時(shí)獲取的類信息是完全一致的搀捷,泛型類型被擦除了狸吞!
如何擦除:
當(dāng)擦除泛型類型后,留下的就只有原始類型了指煎,例如上面的代碼蹋偏,原始類型就是ArrayList
。擦除類型變量至壤,并替換為限定類型(T為無(wú)限定的類型變量威始,用Object替換),如下所示
擦除之前:
//泛型類型
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
擦除之后:
//原始類型
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
因?yàn)樵?code>Pair<T>中像街,T
是一個(gè)無(wú)限定的類型變量黎棠,所以用Object替換。如果是Pair<T extends Number>
镰绎,擦除后脓斩,類型變量用Number類型替換。
與其他語(yǔ)言相比較
相比較Java,其模仿者C#在泛型方面無(wú)疑做的更好畴栖,其是真泛型随静。
C#泛型類在編譯時(shí),先生成中間代碼IL
吗讶,通用類型T
只是一個(gè)占位符燎猛。在實(shí)例化類時(shí),根據(jù)用戶指定的數(shù)據(jù)類型代替T并由即時(shí)編譯器(JIT)生成本地代碼照皆,這個(gè)本地代碼中已經(jīng)使用了實(shí)際的數(shù)據(jù)類型重绷,等同于用實(shí)際類型寫(xiě)的類,所以不同的封閉類的本地代碼是不一樣的膜毁。其可以在運(yùn)行時(shí)通過(guò)反射獲得泛型信息昭卓,而且C#的泛型大大提高了代碼的執(zhí)行效率愤钾。
那么Java為什么不采用類似C#的實(shí)現(xiàn)方式呢,答案是:要向下兼容候醒!兼容害死人啊绰垂。關(guān)于C#與Java的泛型比較,可以查看這篇文章:Comparing Java and C# Generics
總結(jié)
本文追求的目標(biāo)就是讓人看了就懂火焰,包括自己劲装,希望達(dá)到了。怕什么真理無(wú)窮昌简,進(jìn)一寸有一寸的歡喜占业。
參考文章 https://blog.csdn.net/sunxianghuang/article/details/51982979#commentsedit