Java基礎(chǔ)不簡單,泛型很重要!
前言
其實(shí)在開發(fā)中經(jīng)常會(huì)看到泛型的使用,但是很多人對(duì)其也是一知半解,大概知道這是一個(gè)類似標(biāo)簽的東西。比如最常見的給集合定義泛型。
List?list?=?new?ArrayList<>();
Map?map?=?new?HashMap<>();
那么什么是泛型,為什么使用泛型,怎么使用泛型,接著往下看。
什么是泛型Java泛型是J2SE1.5中引入的一個(gè)新特性,其本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)(type parameter),這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法。-- 百度百科
這句話讀起來有點(diǎn)拗口,但是我們要抓住他說的關(guān)鍵,參數(shù)化類型和可以用在類、接口和方法的創(chuàng)建中,我們知道泛型是在什么地方使用。
一般我在思考這種問題時(shí),會(huì)反過來思考,假如沒有泛型會(huì)怎么樣?
我們以最簡單的List集合為例子,假如沒有泛型:
public?static?void?main(String[]?args)?{
????List?list?=?new?ArrayList();
????list.add("good");
????list.add(100);
????list.add('a');
????for(int?i?=?0;?i?????????String?val?=?(String)?list.get(i);
????????System.out.println("val:"?+?val);
????}
}
很顯然在沒有泛型的時(shí)候,List默認(rèn)是Object類型,所以List里的元素可以是任意的,看起來集合里裝著任意類型的參數(shù)是“挺不錯(cuò)”,但是任意的類型的缺點(diǎn)也是很明顯的,就是要開發(fā)者對(duì)集合中的元素類型在預(yù)知的情況下進(jìn)行操作,否則編譯時(shí)不會(huì)提示錯(cuò)誤,但是運(yùn)行時(shí)很容易出現(xiàn)類型轉(zhuǎn)換異常(ClassCastException)。
如果沒有泛型,第二個(gè)小問題是,我們把一個(gè)對(duì)象放進(jìn)了集合中,但是集合并不會(huì)記住這個(gè)對(duì)象的類型,再次取出時(shí)統(tǒng)統(tǒng)都會(huì)變成Object類,但是在運(yùn)行時(shí)仍然為其本身的類型。
所以引入泛型就可以解決以上兩個(gè)問題:
- 類型安全問題。使用泛型,則會(huì)在編譯期就能發(fā)現(xiàn)類型轉(zhuǎn)換異常的錯(cuò)誤。
- 消除類型強(qiáng)轉(zhuǎn)。泛型可以消除源代碼中的許多強(qiáng)轉(zhuǎn)類型的操作,這樣可以使代碼更加可讀,并減少出錯(cuò)的機(jī)會(huì)。
泛型只有在編譯階段有效,在運(yùn)行階段會(huì)被擦除。
下面做個(gè)試驗(yàn),請(qǐng)看代碼:
List?stringArrayList?=?new?ArrayList();
List?integerArrayList?=?new?ArrayList();
Class?classStringArrayList?=?stringArrayList.getClass();
Class?classIntegerArrayList?=?integerArrayList.getClass();
System.out.println(classStringArrayList?==?classIntegerArrayList);
結(jié)果是true,由此可看出,在運(yùn)行時(shí)ArrayList和ArrayList都會(huì)被擦除成ArrayList。
Java 泛型擦除是 Java 泛型中的一個(gè)重要特性,其目的是避免過多的創(chuàng)建類而造成的運(yùn)行時(shí)的過度消耗。
泛型的使用方式在上文也提到泛型有三種使用方式:泛型類、泛型接口、泛型方法。
泛型類
基本語法:
public?class?類名<泛型標(biāo)識(shí),?泛型標(biāo)識(shí),?...>?{
????private?泛型標(biāo)識(shí)?變量名;
}
示例代碼:
public?class?GenericClass<T>?{
????private?T?t;
}
在泛型類里面,泛型形參T可用在返回值和方法參數(shù)上,例如:
public?class?GenericClass<T>?{
????private?T?t;
????public?GenericClass()?{
????}
????public?void?setValue(T?t)?{//作為參數(shù)
????????this.t?=?t;
????}
????public?T?getValue()?{//作為返回值
????????return?t;
????}
}
當(dāng)我們創(chuàng)建類實(shí)例時(shí),就可以傳入類型實(shí)參:
public?static?void?main(String[]?args)?throws?Exception?{
????//泛型傳入了String類型
????GenericClass?generic?=?new?GenericClass();
????//這里就限制了setValue()方法只能傳入String類型
????generic.setValue("abc");
????//限制了getValue()方法得到的也是String類型
????String?value?=?generic.getValue();
????System.out.println(value);
}
這里與普通類創(chuàng)建實(shí)例不同的地方在于,泛型類的構(gòu)造需要在類名后面添加上<泛型>,這個(gè)尖括號(hào)傳的是什么類型,T就代表什么類型。泛型類的作用就體現(xiàn)在這里,他限制了這個(gè)類的使用了泛型標(biāo)識(shí)的方法的返回值和參數(shù)。
泛型方法
基本語法:
修飾符??返回值類型?方法名(形參列表){
}
示例:
//靜態(tài)方法
public?static??E?getObject1(Class?clazz) ?throws?Exception?{
????return?clazz.newInstance();
}
//實(shí)例方法
public??E?getObject2(Class?clazz) ?throws?Exception?{
????return?clazz.newInstance();
}
使用示例:
public?static?void?main(String[]?args)?throws?Exception?{
????GenericClass?generic?=?new?GenericClass<>();
????Object?object1?=?GenericClass.getObject1(Object.class);
????System.out.println(object1);
????Object?object2?=?generic.getObject2(Object.class);
????System.out.println(object2);
}
其實(shí)泛型方法比較簡單,就是在返回值前加上尖括號(hào)<泛型標(biāo)識(shí)>來表示泛型變量。不過對(duì)于初學(xué)者來說,很容易會(huì)跟泛型類的泛型方法混淆,特別是泛型類里定義了泛型方法的情況。
public?class?GenericClass<T>?{
????private?T?t;
????public?GenericClass()?{
????}
?//泛型類的方法
????public?void?setValue(T?t)?{
????????this.t?=?t;
????}
?//泛型類的方法
????public?T?getValue()?{
????????return?t;
????}
?//靜態(tài)泛型方法,區(qū)別在于在返回值前需要加上尖括號(hào)<泛型標(biāo)識(shí)>
????public?static??E?getObject1(Class?clazz) ?throws?Exception?{
????????return?clazz.newInstance();
????}
?//實(shí)例泛型方法,區(qū)別在于在返回值前需要加上尖括號(hào)<泛型標(biāo)識(shí)>
????public??E?getObject2(Class?clazz) ?throws?Exception?{
????????return?clazz.newInstance();
????}
}
泛型接口
基本語法:
public?接口名稱?<泛型標(biāo)識(shí),?泛型標(biāo)識(shí),?...>{
????泛型標(biāo)識(shí)?方法名();
}
示例:
public?interface?Generator<T>?{
????T?next();
}
當(dāng)實(shí)現(xiàn)泛型接口不傳入實(shí)參時(shí),與泛型類定義相同,需要將泛型的聲明加在類名后面:
public?class?ObjectGenerator<T>?implements?Generator<T>?{
????@Override
????public?T?next()?{
????????return?null;
????}
}
當(dāng)實(shí)現(xiàn)泛型接口傳入實(shí)參時(shí),當(dāng)泛型參數(shù)傳入了具體的實(shí)參后,則所有使用到泛型的地方都會(huì)被替換成傳入的參數(shù)類型。比如下面的例子中,next()方法的返回值就變成了Integer類型:
public?class?IntegerGenerator?implements?Generator<Integer>?{
????@Override
????public?Integer?next()?{
????????Random?random?=?new?Random();
????????return?random.nextInt(10);
????}
}
泛型通配符
類型通配符一般是使用 ? 代替具體的類型實(shí)參,這里的 ? 是具體的類型實(shí)參,而不是類型形參。它跟Integer,Double,String這些類型一樣都是一種實(shí)際的類型,我們可以把 ? 看作是所有類型的父類。
類型通配符又可以分為類型通配符上限和類型通配符下限。
類型通配符上限
基本語法:
類/接口?extends?實(shí)參類型>
要求該泛型的類型,只能是實(shí)參類型,或?qū)崊㈩愋偷?子類 類型。
比如:
public?class?ListUtils<T?extends?List>?{
????public?void?doing(T?t)?{
????????System.out.println("size:?"?+?t.size());
????????for?(Object?o?:?t)?{
????????????System.out.println(o);
????????}
????}
}
這樣就限制了傳入的泛型只能是List的子類,如下所示:
public?static?void?main(String[]?args)?throws?Exception?{
????ListUtils?utils?=?new?ListUtils<>();
????List?list?=?Arrays.asList("1",?"2",?"3");
????utils.doing(list);
}
類型通配符下限
基本語法:
類/接口?super?實(shí)參類型>
要求該泛型的類型,只能是實(shí)參類型,或?qū)崊㈩愋偷?父類 類型
比如:
public?class?ListUtils?{
????//靜態(tài)泛型方法,使用super關(guān)鍵字定義通配符下限
????public?static??void?copy(Collection?super?E>?parentList,?Collection?childList) ?{
????????for?(E?e?:?childList)?{
????????????parentList.add(e);
????????}
????}
}
限制了傳入的List類型:
public?static?void?main(String[]?args)?throws?Exception?{
????Integer?i1?=?new?Integer(10);
????Integer?i2?=?new?Integer(20);
????List?integers?=?new?ArrayList<>();
????integers.add(i1);
????integers.add(i2);
????List?numbers?=?new?ArrayList<>();
????//childList傳入的是List,所以parentList傳入的只能是Integer本身或者其父類的List
????ListUtils.copy(numbers,?integers);
????System.out.println(numbers);//[10,?20]
}
泛型使用注意點(diǎn)
不能在靜態(tài)字段使用泛型。

不能和基本類型一起使用。

異常類不能使用泛型。
總結(jié)最后總結(jié)一下泛型的作用:
- 提高了代碼的可讀性,這點(diǎn)毋庸置疑。
- 解決類型安全的問題,在編譯期就能發(fā)現(xiàn)類型轉(zhuǎn)換異常的錯(cuò)誤。
- 可拓展性強(qiáng),可以使用泛型通配符定義,不需要定義實(shí)際的數(shù)據(jù)類型。
- 提高了代碼的重用性。可以重用數(shù)據(jù)處理算法,不需要為每一種類型都提供特定的代碼。
非常感謝你的閱讀,希望這篇文章能給到你幫助和啟發(fā)。
覺得有用就點(diǎn)個(gè)贊吧,你的點(diǎn)贊是我創(chuàng)作的最大動(dòng)力~
我是一個(gè)努力讓大家記住的程序員。我們下期再見!!!
能力有限,如果有什么錯(cuò)誤或者不當(dāng)之處,請(qǐng)大家批評(píng)指正,一起學(xué)習(xí)交流!
