什么是泛型
泛型是Java SE 1.5的新特性,泛型的本質(zhì)是參數(shù)化類型,也就是說所操作的數(shù)據(jù)類型被指定為一個參數(shù)。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法。 Java語言引入泛型的好處是安全簡單。
泛型有什么好處
在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實(shí)現(xiàn)參數(shù)的“任意化”,“任意化”帶來的缺點(diǎn)是要做顯式的強(qiáng)制類型轉(zhuǎn)換,而這種轉(zhuǎn)換是要求開發(fā)者對實(shí)際參數(shù)類型可以預(yù)知的情況下進(jìn)行的。對于強(qiáng)制類型轉(zhuǎn)換錯誤的情況,編譯器可能不提示錯誤,在運(yùn)行的時候才出現(xiàn)異常,這是一個安全隱患。
泛型的好處是在編譯的時候檢查類型安全,并且所有的強(qiáng)制轉(zhuǎn)換都是自動和隱式的,提高代碼的重用率。
1、泛型的類型參數(shù)只能是類類型(包括自定義類),不能是簡單類型。
2、同一種泛型可以對應(yīng)多個版本(因為參數(shù)類型是不確定的),不同版本的泛型類實(shí)例是不兼容的。
3、泛型的類型參數(shù)可以有多個。
4、泛型的參數(shù)類型可以使用extends語句,例如<T extends superclass>。習(xí)慣上稱為“有界類型”。
5、泛型的參數(shù)類型還可以是通配符類型。例如Class<?> classType = Class.forName("java.lang.String");
沒有泛型的時候是這樣的
ArrayList al = new ArrayList();
al.add("ysjian001");
al.add(1);
al.add(new Object());
這段代碼看似功能強(qiáng)大,為什么呢?因為它似乎能夠往集合添加各種類型的對象(int類型會被裝箱成Integer對象類型),貌似一些老程序員也傾向于這么去做,而且他們可以理直氣壯的告訴我理由:我這么做想存什么就存什么!先不否定這種說法,讓我們繼續(xù),看看下面代碼:
String first = (String) al.get(0);
往集合里面存值就是為了后期取出來用的,而不是System.out.println(first),這里就產(chǎn)生了一個強(qiáng)制轉(zhuǎn)換的問題,而往往這種類型的強(qiáng)制轉(zhuǎn)換在編譯器是允許通過的,而寫程序的人們會犯下無意間的錯誤,錯誤的進(jìn)行了強(qiáng)制轉(zhuǎn)換,導(dǎo)致程序運(yùn)行失敗。
強(qiáng)制類型轉(zhuǎn)換導(dǎo)致的程序運(yùn)行失敗的原因是沒有在編譯器對類型進(jìn)行控制,看看code01調(diào)用ArrayList對象的add方法,任何類型都是可以添加進(jìn)行的,編譯器無法進(jìn)行錯誤檢驗,埋下了安全隱患,例如:
ArrayList al = new ArrayList();
// 無法進(jìn)行錯誤檢查,F(xiàn)ile對象可以添加進(jìn)去,編譯器和運(yùn)行期都可以通過
al.add(new File());
String first = (String) al.get(0); // 類型轉(zhuǎn)換失敗導(dǎo)致運(yùn)行失敗
不用泛型的缺點(diǎn)
沒有泛型的程序面臨兩個問題:
- 編譯器無法進(jìn)行類型檢查,可以向集合中添加任意類型的對象。
- 取值時類型轉(zhuǎn)換失敗導(dǎo)致程序運(yùn)行失敗。
沒有泛型的程序?qū)е碌暮蠊?/p>
- 程序的可讀性有所降低,因為程序員可以不受限制往集合中添加任意對象。
- 程序的安全性遭到質(zhì)疑,類型轉(zhuǎn)換失敗將導(dǎo)致程序運(yùn)行失敗。
Java5泛型提供了一個更好的解決方案:類型參數(shù)(type parameters),使用泛型的程序改善上述代碼如下:
ArrayList<String> al = new ArrayList<String>();
al.add( "ysjian001");
// al.add(new Thread()); // 定義了String類型參數(shù),添加File對象會報錯
String first = al.get(0);// 使用泛型后取值不用進(jìn)行類型轉(zhuǎn)換
到這里,通過前后對比,泛型的好處是不是很清楚了呢?為什么用泛型呢?
因為出現(xiàn)編譯錯誤比類在運(yùn)行時出現(xiàn)強(qiáng)制類型轉(zhuǎn)換異常要好得多,泛型的好處在于提高了程序的可讀性和安全性,這也是程序設(shè)計的宗旨之一。
什么時候使用泛型
使用泛型類是一件很輕松的事,集合框架中的類都是泛型類,用起來很方便。有人會想類型限制我們?yōu)槭裁床恢苯佑脭?shù)組呢?這個問題就好像問為什么集合優(yōu)于數(shù)組,數(shù)組是固定的,而集合是可以自動擴(kuò)展的。另外在實(shí)際中,實(shí)現(xiàn)一個泛型其實(shí)并不是那么容易。
大多數(shù)應(yīng)用程序員對泛型的熟練程度僅僅停留在使用泛型上,像集合類中的List、Set和Map這些泛型集合用的很多,他們不必考慮這些泛型集合的工作方式和原理。那么當(dāng)把不同的泛型類混合在一起使用時,或者對Java5之前的遺留代碼進(jìn)行銜接時,可能會看到含糊不清的的錯誤消息。這樣一來,程序員就需要學(xué)習(xí)Java泛型來解決問題了,而不是在程序中胡亂猜測了。最終,部分程序員可能想要實(shí)現(xiàn)自己的泛型類和泛型方法。
提煉出泛型程序設(shè)計的三種熟練程度就是:
- 僅僅使用泛型。
- 學(xué)習(xí)泛型解決一些問題。
- 掌握泛型,實(shí)現(xiàn)自己的泛型。
怎么使用泛型
如何使用泛型聽起來是一件很容易的事情,因為Sun公司的那些工程師已經(jīng)做了很大努力,而需求總是會稍微苛刻一點(diǎn)的,需要解決因為缺乏類型參數(shù)模糊不清的問題,或者我們有必要實(shí)現(xiàn)自己的泛型來滿足業(yè)務(wù)需求,所以學(xué)習(xí)和掌握泛型是很有必要的。最常見的自定義泛型的場景是泛型類、泛型方法、泛型接口。
泛型類
把泛型定義在類上
格式:public class 類名<泛型類型1,…>
注意:泛型類型必須是引用類型
早期的時候,我們使用Object來代表任意的類型。
向上轉(zhuǎn)型是沒有任何問題的,但是在向下轉(zhuǎn)型的時候其實(shí)隱含了類型轉(zhuǎn)換的問題。
也就是說這樣的程序其實(shí)并不是安全的。所以Java在JDK5后引入了泛型,提高程序的安全性。
下面我們就來學(xué)習(xí)泛型類是怎么回事
// 泛型類:把泛型定義在類上
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
// 泛型類的測試
public class ObjectToolDemo {
public static void main(String[] args) {
ObjectTool<String> ot = new ObjectTool<String>();
ot.setObj(new String("中國"));
String s = ot.getObj();
System.out.println("姓名是:" + s);
ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
ot2.setObj(new Integer(69));
Integer i = ot2.getObj();
System.out.println("年齡是:" + i);
}
}
輸出結(jié)果:
姓名是:中國
年齡是:69
泛型方法
把泛型定義在方法上
格式:public <泛型類型> 返回類型 方法名(泛型類型 .)
上面我們把泛型定義在了類中,現(xiàn)在我們也可以把泛型定義在方法中,來一起學(xué)習(xí)
* * 泛型方法:把泛型定義在方法上 **
public class ObjectTool {
public <T> void show(T t) {
System.out.println(t);
}
}
public class ObjectToolDemo {
public static void main(String[] args) {
// 定義泛型方法后
ObjectTool ot = new ObjectTool();
ot.show("hello");
ot.show(100);
ot.show(true);
}
}
這樣我們就可以傳遞任意類型的參數(shù)了
泛型接口
把泛型定義在接口上
格式:public interface 接口名<泛型類型1…>
/* * 泛型接口:把泛型定義在接口上 */
public interface Inter<T> {
public abstract void show(T t);
}
//實(shí)現(xiàn)類在實(shí)現(xiàn)接口的時候,我們會遇到兩種情況
//第一種情況:已經(jīng)知道是什么類型的了
public class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
//第二種情況:還不知道是什么類型的
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
public class InterDemo {
public static void main(String[] args) {
// 第一種情況的測試
Inter<String> i = new InterImpl();
i.show("hello");
// 第二種情況的測試
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
}
}
我們來寫一個簡單的例子驗證一下上面所說的結(jié)論
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明確的寫的時候,前后必須一致 Collection<Object> c1 = new ArrayList<Object>();
// Collection<Object> c2 = new ArrayList<Animal>();//報錯
// Collection<Object> c3 = new ArrayList<Dog>();//報錯
// Collection<Object> c4 = new ArrayList<Cat>();//報錯
// ?表示任意的類型都是可以的
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子類
// Collection<? extends Animal> c9 = new ArrayList<Object>();//報錯
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E極其父類
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();//報錯
// Collection<? super Animal> c16 = new ArrayList<Cat>();//報錯
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
仔細(xì)觀察一下上面的通配符有什么區(qū)別,你會很快的學(xué)會通配符的使用
泛型在實(shí)際開發(fā)中的應(yīng)用場景
泛型在實(shí)際開發(fā)中用的很多,比如常見的Dao層的父類BaseDao,這里面一般是一個泛型類,把實(shí)體作為通配符傳過去。