Java泛型
java的泛型並不是新的特性,在JDK1.5時就有的操作,其實我們一直在使用泛型,常見的List,Map中都有它的身影
那它有什麼用處呢?以我們最常用的ArrayList為例
public void test1() {
ArrayList arrayList=new ArrayList();
arrayList.add("張三");
arrayList.add("李四");
arrayList.add("王五");
for (Object o : arrayList) {
String name= (String) o;
System.out.println(name);
}
}
來看它的get方法返回型別
我們都知道,List中可以儲存資料,那麼上面的程式碼看起來沒有問題,執行也沒有問題
但是,如果我們往list新增個int型別的資料,那麼在下面強制轉換輸出時,就會報出ClassCastException
異常
原因也很簡單,我們不能把一個int型別的變數強轉為String型別,那麼問題也很明顯了,List的安全性
有解決方法嗎,當然有,對於每個型別單獨建立對應的List,每個List中只能存放對應的型別,這樣雖然能解決問題,但是重複了大量相同程式碼,重複造輪子
所以在JDK1.5的時候,出現了一個新特性 : 泛型(genericity)
程式不能確定執行時的型別,但是使用Object來表示型別又太過籠統
比如北京大學旁邊的理髮店,理髮店辦活動,北京大學的學生來理髮有優惠,那麼理髮店如何來指定那些人有優惠呢?
人類有優惠?範圍太大.
具體的某個人有優惠?那麼理髮店把北京大學所有人名列印貼在牆上?這又太過於具體.
所以JDK想指定一些不確定的型別,但是又不想太過於籠統,這就使用到了泛型
泛型本質是將型別引數化,通過傳入的型別來判斷當前執行的型別,而泛型又分為泛型類、泛型介面、泛型方法
我們以常見的ArrayList為例:
public class ArrayList<E> ...{
public boolean add(E e) {...}
public E get(int index) {...}
}
我們可以看到,在類名後面新增了一對尖括號<>裡面有一個大寫字母E 這就表示ArrayList為一個泛型類,括號中可以寫多個泛型
在例項化時可以通過new ArrayList<>後面的尖括號中,來新增這個List中使用的型別
ArrayList<String> arrayList=new ArrayList<String>();
很明顯,當我們設定完List的使用型別後,我們add Int型別的方法出現了異常
idea提示我們所需的型別是一個String型別,而我們提供的是一個int型別,這也說明了泛型具有檢查的型別的功能
在JDK1.7之後,如果接收型別中顯式設定了型別,那麼例項化物件可以不用寫具體的型別,只需要寫一對尖括號即可
ArrayList<String> arrayList=new ArrayList<>();
那麼設定完泛型後,我們在來看get方法的返回型別
這裡可以看到,在我們設定完List的泛型後,返回的型別已經為具體的String類了
那麼它是如何實現動態的型別變化的呢?
我們來自己建立一個泛型類,一切就都知道了
醫院類
public class Hospital<T> {
/**
* 輸出動物的資訊方法
* @param t
*/
public void print(T t){
System.out.println(t);
}
/**
* 給寵物打疫苗方法
* @return T
*/
public T vaccine(T t){
System.out.println("給寵物打疫苗");
return t;
}
}
貓類
public class Cat {
private int age;
private String name;
//省略構造,get,set,toString方法
}
當然,上面這個例子可能不是太好,這種情況完全可以抽出父類Animal類進行多型的實現,但是我們現在僅僅為了學習泛型
那麼上面醫院類Hospital的< T > 以及下面兩個方法中的T是什麼意思呢?
我們可以想象為,當例項化Hospital醫院類時候,通過new Hospital< Cat>(); 將貓Cat類傳入到Hospital類中,那麼在這個已經例項化的Hospital物件中,所有的T都代表為傳入的Cat類,簡單點說就是,傳入啥型別T就是啥型別
那麼T是java的關鍵字嗎,並不是,我們可以任意起名,就像和給變數起名一樣,但是泛型有些規範我們要儘量遵守
java泛型字母代表意思
E | Element (在集合中使用,因為集合中存放的是元素) |
---|---|
T | Type(Java 類) |
K | Key(鍵) |
V | Value(值) |
N | Number(數值型別) |
public void test1() {
Hospital<Cat> catHospital = new Hospital<>();
catHospital.print(new Cat(5,"小花"));
}
我們可以看到,當設定完泛型後,我們可以動態實現對於一個類的不同實現
繼承關係
宣告泛型的類和普通的類沒有太大的區別,同樣可以使用繼承
可能會出現子類保留,指定,新增泛型的情況
對於上面這幾種情況直接概況一下
- 如果子類沒有保留父類泛型,那麼父類泛型預設為Object
- 如果指定父類泛型的型別,那麼父類泛型型別為指定的型別
- 如果子類保留了父類型別,那麼泛型型別就為子類例項化時設定的型別
什麼意思呢?我們來看
這種情況在例項化Son物件時,設定AB的泛型型別,那麼Son中和Father中的型別就會是設定的型別
比如設定為String,Integer,那麼子類和父類中的泛型型別都為String和Integer
//父類
public class Father<A,B> {
}
//子類
public class Son<A,B> extends Father<A,B>{
public void print(A a,B b){
System.out.println("Son-A的型別:"+a.getClass());
System.out.println("Son-B的型別:"+b.getClass());
}
}
//測試
public void test1() {
Son<String, Integer> son = new Son<>();
son.print("",1);
}
//結果
Son-A的型別:class java.lang.String
Son-B的型別:class java.lang.Integer
除了子類保留父類,還可以直接指定父類泛型型別
//父類
public class Father<A,B> {
}
//子類 在這裡直接設定了父類泛型型別 ↓
public class Son<A,B> extends Father<Integer,B>{
public void print(A a,B b){
System.out.println("Son-A的型別:"+a.getClass());
System.out.println("Son-B的型別:"+b.getClass());
}
}
那麼在例項化子類時即使設定new Son<String,String>(); 那麼父類的兩個泛型型別只會是Integer和String,因為泛型A已經在子類中指定了
還有一種沒有保留父類泛型型別
//不保留也不指定父類泛型型別 ↓ 去掉這裡的 <A,B>
public class Son<A,B> extends Father{
public void print(A a,B b){
System.out.println("Son-A的型別:"+a.getClass());
System.out.println("Son-B的型別:"+b.getClass());
}
}
如果不保留父類泛型型別,也不指定父類泛型型別,那麼泛型型別預設為Object相當於extends Father<Object,Object>
如果部分保留,除非子類需要的話,那麼子類只需要宣告保留的泛型即可
泛型介面 和泛型類繼承一致,這裡不再贅述
泛型方法
泛型方法不一定存在泛型類或泛型介面中,只對方法而言,傳入的引數型別不確定
例如下面這個getMax方法,如果是Integer返回最大值,如果是String根據ASII碼返回最大值,假設傳入List中的型別為String或Integer
public <E> E getMax(List<E> es){
if(es==null || es.size()==0)
return null;
E e = es.get(0);
if (e instanceof Integer) {
//獲取最大值操作...
}
if(e instanceof String){
//獲取最大值操作...
}
return null;
}
我們可以看到,傳入的引數並不確定,這種方法可以稱之為泛型方法,泛型方法怎麼宣告呢?
//在返回型別前面加上<X> X可以換成任何字母,但是儘量符合規範
//那麼在返回型別,傳入引數型別,方法體中都可以使用這個定義泛型
public <E> List<E> get(E[] arrays){
}
?萬用字元
?表示未知型別,
例如List<?> 那麼任何型別的List都可以賦值給這個List,但是注意,並不能直接往這個List中新增資料
也有個例外,新增null可以
public void test1() {
List<?> list=new ArrayList<>();
// list.add("小明"); 報錯 需要的型別為 ? 而我們傳入型別為String
//但是下面這些賦值都沒有問題
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
list = stringList;
list = integerList;
}
List<?> 的get方法,獲取的型別是Object
萬用字元集合可以作為方法的形參,來接收List集合中不確定的型別,例如下面這個例子
//這裡使用迭代器來進行遍歷
public void print(List<?> list){
ListIterator<?> listIterator = list.listIterator();
while (listIterator.hasNext()){
Object next = listIterator.next();
System.out.println(next);
}
}
這裡有需要注意的點,例如X類為Y類的父類,泛型genericity 簡稱G G< Y >並不能直接賦值給G< X >,例如下面這個例子
String為Object的子類,為什麼不能賦值給Object的集合呢?
這裡我們需要將它們想象成兩個類,雖然在執行中只是一個類,我們可以想象為一個類只為Object提供服務,另一個只為String提供服務,雖然Object和String有繼承關係,但是它們兩個集合並沒有任何關係,只是功能相同而已,並不存在List