1、什么是泛型
所謂泛型珊蟀,就是允許在定義類菊值、接口、方法時使用類型形參育灸,這個類型形參將在聲明變量腻窒、創(chuàng)建對象、調(diào)用方法時動態(tài)的指定磅崭。
2儿子、為什么要使用泛型
先看下這個例子:
public static void main(String[] args) {
List list = new ArrayList();
list.add("字符串");
list.add(122);
for (int i = 0; i <list.size() ; i++) {
String str = (String) list.get(i);
System.out.println(str);
}
}
我們運(yùn)行上面的代碼,就會拋出如下的異常绽诚。
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
我們知道ArrayList
可以存放任意對象類型典徊,我們先向list
中存放了一個String
類型的數(shù)據(jù)杭煎,接著又存放了一個Integer
類型的數(shù)據(jù)恩够,在使用的時候就拋出了異常。為了解決類似這樣的問題羡铲,泛型應(yīng)運(yùn)而出蜂桶。
對于上面的例子的例子做如下改造:
List<String> list = new ArrayList();
list.add("字符串");
list.add(122); // add(java.long.String) in list connot be applied to (int)
當(dāng)我們使用泛型后,再存放Integer
類型的數(shù)據(jù)也切,就會直接報(bào)錯扑媚,這就是再編譯時檢測,而上面的是在運(yùn)行時才去檢測的雷恃,這樣就能很大程度的解決程序員犯錯了疆股。
與非泛型代碼相比,使用泛型的代碼具有許多優(yōu)點(diǎn):
-
在編譯時進(jìn)行更強(qiáng)的類型檢查倒槐。
Java
編譯器將強(qiáng)類型檢查應(yīng)用于泛型代碼旬痹,并在代碼違反類型安全時發(fā)出錯誤。修復(fù)編譯時錯誤比修復(fù)運(yùn)行時錯誤更容易讨越,運(yùn)行時錯誤很難找到两残。 - 消除類型轉(zhuǎn)換。 當(dāng)使用的時候不需要強(qiáng)制轉(zhuǎn)換類型把跨。
- 使程序員能夠?qū)崿F(xiàn)泛型算法人弓。 通過使用泛型,程序員可以實(shí)現(xiàn)泛型算法着逐,這些算法可以處理不同類型的集合崔赌,可以自定義湿痢,并且類型安全且易于閱讀分飞。
3、泛型的使用
泛型有三種使用方式,分別為:泛型類妄辩、泛型接口、泛型方法郊楣。
3.1操刀、泛型類
泛型類型用于類的定義中,就稱之為泛型類吩翻,泛型類的基本寫法如下:
public class Generic<T> {}
public class Generic<T1,T2,...,Tn> {}
在類名之后兜看,類型參數(shù)部分由尖括號<>分隔。它指定了類型參數(shù)(也稱為類型變量)T狭瞎,當(dāng)然了也可以定義多個參數(shù)细移。
下面看一個例子:
/**
* 在實(shí)例化泛型類時,必須指定T的具體類型
* @param <T> 此處T可以隨便寫為任意標(biāo)識熊锭,常見的如T弧轧、E、K碗殷、V等形式的參數(shù)常用于表示泛型
*/
public class Generic<T> {
//key這個成員變量的類型為T,T的類型由外部指定
private T key;
//泛型構(gòu)造方法形參key的類型也為T精绎,T的類型由外部指定
//這里需要注意的是構(gòu)造器名是Generic ,而不是Generic<T>锌妻。
public Generic(T key) {
this.key = key;
}
//泛型方法getKey的返回值類型為T代乃,T的類型由外部指定
public T getKey() {
return key;
}
}
運(yùn)行下面代碼:
Generic<String> genericStr = new Generic<>("測試泛型類");
Generic<Integer> genericInt = new Generic<>(100);
System.out.println("泛型測試---》"+genericStr.getKey());
System.out.println("泛型測試---》"+genericInt.getKey());
// 運(yùn)行結(jié)果如下:
// 泛型測試---》測試泛型類
// 泛型測試---》100
對于多種類型的參數(shù)其實(shí)是一樣的做法,比如我們熟悉的HashMap
就是多種類型參數(shù):
//HashMap的源碼
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {...}
//HashMap的使用
Map<String,String> map = new HashMap<>();
3.2仿粹、泛型接口
其實(shí)泛型接口和泛型類的定義使用基本上是一樣的搁吓。模擬我們常用的list接口來演示下泛型接口
//定義一個泛型接口
public interface MyList<T> {
public T add(T t);
}
當(dāng)實(shí)現(xiàn)泛型接口的類,未傳入泛型實(shí)參時吭历,與泛型類的定義相同堕仔,在聲明類的時候,需將泛型的聲明也一起加到類中
public class MyArrayList<T> implements MyList<T> {
@Override
public T add(T t) {
return t;
}
public static void main(String[] args) {
MyList<String> list = new MyArrayList<>();
list.add("aaaa");
MyList<Integer> list1 = new MyArrayList<>();
list1.add(0);
}
}
在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時晌区,如已將泛型類型傳入實(shí)參類型摩骨,則所有使用泛型的地方都要替換成傳入的實(shí)參類型。
public class MyArrayList implements MyList<String> {
@Override
public String add(String s) {
return s;
}
}
3.3契讲、泛型方法
泛型方法仿吞,是在調(diào)用方法的時候指明泛型的具體類型〖衿基本寫法如下:
/**
* @param <T> 這個<T>非常重要唤冈,可以理解為聲明此方法為泛型方法。
* 只有聲明了<T>的方法才是泛型方法银伟。泛型類中的使用了泛型的成員方法并不是泛型方法你虹。
*/
public <T> void Generic(T t) {}
下面看下具體例子看下泛型方法的實(shí)現(xiàn):
public class TestGeneric {
public <T> void getKey(T t) {
System.out.println("測試泛型方法---》" +t);
}
public static void main(String[] args) {
TestGeneric testGeneric = new TestGeneric();
testGeneric.getKey("字符串");
testGeneric.getKey(0);
}
}
//輸出結(jié)果
//測試泛型方法---》字符串
//測試泛型方法---》0
3.4绘搞、泛型通配符
稱為通配符的問號(?)表示未知類型。通配符可以在多種情況下使用:作為參數(shù)傅物,字段或局部變量的類型夯辖;有時作為返回類。通配符從不用作泛型方法調(diào)用董饰,泛型類實(shí)例創(chuàng)建或超類型的類型參數(shù)蒿褂。下面詳細(xì)地討論通配符,包括上界通配符卒暂,下界通配符和通配符捕獲啄栓。
下面我們看一個例子:
List<?> list = new ArrayList<String>();
//我們可以正常獲取list中的元素,不會引起編譯錯誤
Object object = list.get(0);
//調(diào)用add方法會引起編譯錯誤
list.add("字符串");
這種帶通配符的 List
僅表示它是各種泛型List
的父類也祠,并不能把元素添加到其中昙楚,否則會引起編譯錯誤。因?yàn)槌绦驘o法確定list
集合中的元素類型诈嘿,所以不能向其中添加對象堪旧,而程序第哦啊用get()
方法來返回List<?>
集合指定索引處的元素,其返回值是一個未知類型奖亚,但肯定是一個Object
淳梦,因此返回值賦給一個Object
類型的變量是可以的。
3.4.1遂蛀、上界通配符
為泛型添加上邊界谭跨,即傳入的類型實(shí)參必須是指定類型的子類型干厚,要聲明上界通配符李滴,請使用通配符(?),然后使用extends
關(guān)鍵字
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(1);
List<Double> doubles = new ArrayList<>();
doubles.add(2.0d);
List<String> strings = new ArrayList<>();
strings.add("通配符上界");
print(integers);
print(doubles);
//編譯直接報(bào)錯
//print(strings);
}
public static void print(List<? extends Number> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
3.4.2蛮瞄、下界通配符
通配符將未知類型限制為特定類型,下界通配符表示為通配符(?)所坯,后跟`super關(guān)鍵字
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(0);
List<Number> numbers = new ArrayList<>();
numbers.add(123);
numbers.add(10.0d);
List<Double> doubles = new ArrayList<>();
doubles.add(0.2d);
print(integers);
print(numbers);
//編譯報(bào)錯
//print(doubles);
}
public static void print(List<? super Integer> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
上面我們可以看到定義了下界Integer
后,我們就無法在使用Double
類型了挂捅,只能使用Integer
類型芹助、Number
類型、Object
類型了闲先。
4状土、泛型總結(jié)
使用泛型的時候,要考慮泛型的限制:
- 無法實(shí)例化具有基本類型的泛型類型
- 無法創(chuàng)建類型參數(shù)的實(shí)例
- 無法聲明類型為類型參數(shù)的靜態(tài)字段
- 無法創(chuàng)建參數(shù)化類型的數(shù)組
- 無法創(chuàng)建捕獲或拋出參數(shù)化類型的對象
- 無法重載每個重載的形式參數(shù)類型都擦除為相同原始(raw)類型的方法伺糠。
- 無法將
Casts
或instanceof
與參數(shù)化類型一起使用
泛型蒙谓,我們用到最多的可能就是集合了。在實(shí)際開發(fā)中训桶,我們合理的使用泛型可以簡化我們的開發(fā)累驮,提高為我們的開發(fā)效率酣倾。