概述
集合可以存儲任意類型的對象,對象存入集合以後都被提升爲Object類型。當我從集合中取出對象的時候都需要進行強制轉型來後續操作對象。
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a11");
list.add(1);
list.add("1");
for (Object o : list) {
String str = (String) o;
System.out.println(str);
}
}
這段簡單的演示代碼就會出現題java.lang.ClassCastException
的運行時期異常。通常遇到這種情況可以使用instanceof
來判斷是否爲某種類型或是其子類型,但這樣會增加代碼複雜度,且不能徹底解決問題,畢竟再怎麼考慮終會有所遺漏的類型不能完成正常轉換。
JDK1.5之後引入了**泛型(Generic)**語法。這樣我們在使用設計API的時候可以指定類或者方法甚至接口的泛型,設計出的API也更爲簡潔,並且也能在編譯時期查出異常。
使用泛型的好處
- 避免強制類型轉換問題
- 將運行時異常提前到編譯時期,減少了後期修改bug的工作量
- 一旦指定泛型,數據類型將被統一
- 實現代碼的模塊化,把數據類型當做參數
泛型的定義和使用
1、泛型類的定義
(1)格式
public class 類名<泛型變量>{
...
}
泛型類中的泛型一旦被指定,整個類除了泛型方法,全部帶有泛型的類型佔位符的地方都會被替換爲指定的類型。
(2)使用範例
public class MyClass<T> {
private T t;
public MyClass() {
}
public MyClass(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public static void main(String[] args) {
// 空參構造創造對象
MyClass<String> mc1 = new MyClass<>();
mc1.setT("Hello");
String t = mc1.getT();
System.out.println(t);
System.out.println("-----------------------------------");
// 滿參構造,尖括號指定了什麼類型就傳遞什麼類型數據
MyClass<Integer> mc2 = new MyClass<>(1);
String t = mc1.getT();
System.out.println(t);
}
2、泛型方法的定義
在泛型類使用中一般默認直接把泛型佔位符替換爲統一指定的類型,但有時候需要方法定義自己特定的泛型。
(1)格式
修飾符 <T> 返回值類型 方法名稱(T t,參數列表...){
方法體...
}
注意事項:
- 前面
<T>
在定義方法的泛型 - 後面方法參數:
T t
使用的是方法自己的的泛型
(2)使用範例
public class MyClass02<T> {
// 不是泛型方法
public void print(T t){
System.out.println(t);
}
// 泛型方法,定義了自己的泛型,使用佔位符E表示泛型
public <E> void show(E e){
System.out.println(e);
}
}
public static void main(String[] args) {
MyClass02<String> mc = new MyClass02<>();
mc.print("Hello");
// print 使用的是類上的泛型,已經確定爲String類型了
//錯誤:mc.print(10000);
mc.show(18);
}
3、泛型接口的定義
(1)格式
public interface 接口名稱<T> {
...
}
(2)接口上定義的泛型,什麼時間確定具體類型
- 定義實現類時,直接確定接口上泛型的具體類型
public interface MyInter<T> {
public abstract void show(T t);
}
/*
定義實現類時,直接確定接口上泛型的具體類型
*/
public class MyInterImpl implements MyInter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
- 定義實現類時不確定接口上泛型的具體類型,那麼該實現類必須定義爲泛型類,而且實現類上的泛型變量要和接口上的泛型變量名稱一致——創建實現類對象時,<>中要寫是什麼類型,泛型就是什麼類型。這也叫做泛型的傳遞。
public interface MyInter<T> {
public abstract void show(T t);
}
/*
定義實現類時不確定接口的類型
*/
public class MyInterImplB<T> implements MyInter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
泛型的通配符
代表任意的一種引用類型,只能用來匹配泛型,不能用來定義泛型。
(1)注意事項:
- 泛型是不存在多態的,創建集合對象時,左右兩邊的<>內容要保持一致
- ArrayList< ? > list 可以接受什麼?
可以接受ArrayList的任意類型對象(只要在創建ArrayList集合對象中<>寫上一種引用類型,都是可以的)
(2)示例
public static void main(String[] args) {
ArrayList<String> l1 = new ArrayList<>();
Collections.addAll(l1,"123-123qwe-qwe-qwe-fhj-ghj-yjw-wlt-gfd-iop".split("-"));
ArrayList<Integer> l2 = new ArrayList<>();
Collections.addAll(l2,4,5,6,13,123,123,123,45,6,7,12,890,7,2,3,30);
print(l1);
print(l2);
}
public static void print(ArrayList<?> arrayList){
System.out.println(arrayList);
}
泛型的上下限
使用泛型的通配符,我們可以接受任何類型的參數,但有時候我們需要更加嚴格的限制傳遞的類型,比如,我們需要傳遞的參數是某個類型或者它的子類,又或是某個類型或者它的父類。此時,我們就需要使用泛型的上下限來做約束。
1、泛型的上限:
格式-類型名稱<? extends E>
:表示E類型或者E類型任意子類
2、泛型的下限:
格式-類型名稱<? super E>
:表示E類型或者E類型的任意父類