Java範型學習筆記
對於範型的使用或者說印象只有集合,其他地方即使使用過也不知道,反正就是隻停留在
List<E>
Map<K, V>
,最近剛好閒來無事,就找找資料學習一下;下列為個人學習總結,歡迎學習交流;
1. 什麼是java泛型
範型:引數化型別,所操作的資料型別被指定為一個引數。這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面、泛型方法;
List<Integer> list = new ArrayList<>();
上述程式碼申明瞭一個集合,操作的資料型別被指定為Integer(此處Integer為型別引數);
2. 為什麼需要泛型
引入泛型的好處是可以將執行時錯誤提前到編譯時錯誤
List list = new ArrayList();
list.add(100);
list.add("zhangsan");
for(int i = 0; i< list.size();i++){
int num = (int)list.get(i);
}
上面的程式碼在編譯時沒有任何問題,但是在執行時會報錯:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
如果沒有引入範型,集合內的操作資料型別可以是任何型別,如果在操作資料時進行型別判斷然後在強轉也是沒有問題的,但是很明顯不切合實際,所以如果引入範型對操作資料型別做一定的約束的話,將會對後續的操作提供太多的方便也能減少錯誤的出現;
List<Integer> list = new arrayList<>();
//list.add("zhangsan");//在編譯期就會報錯
3. 範型的使用
泛型有三種使用方式,分別為:泛型類、泛型介面、泛型方法
3.1. 範型類
泛型類中的型別引數幾乎可以用於任何可以使用介面名、類名的地方
/**
* 範型標識可以是任何識別符號號,如常見的E, T, K, V ...
*/
class 類名<範型標識>{
}
class Stu<T>{
}
class People<E>{
}
注意:
- 泛型的型別引數只能是類型別,不能是基礎型別;
List<int> list;//基礎型別不能當作型別引數
- 不能對確切的泛型型別使用instanceof操作。如下面的操作是非法的,編譯時會出錯。
if(item instanceof List<Integer>) // Illegal generic type of instanceof
3.2. 範型介面
泛型介面與泛型類的定義及使用基本相同
public interface Iterable<T> {
Iterator<T> iterator();
}
- 實現範型介面,未明確範型時:實現類後的範型標識不能省略
class Iter<T> implements Iterable<T> {
@Override
public Iterator<T> iterator() {
return null;
}
}
- 實現範型介面,明確範型時:實現類後的範型標識省略
class Iter1 implements Iterable<String> {
@Override
public Iterator<String> iterator() {
return null;
}
}
3.3. 範型方法
public 與 返回值 中間
非常重要,宣告此方法為泛型方法;
/**
* 泛型方法的基本介紹
* @param tClass 傳入的泛型實參
* @return T 返回值為T型別
* 說明:
* 1)public 與 返回值 中間<T>非常重要,宣告此方法為泛型方法;
* 2)只有聲明瞭<T>的方法才是泛型方法,若沒有<T>泛型類中即使使用了泛型的成員方法也不是泛型方法;
* 3)<T>表明該方法將使用泛型型別T,此時才可以在方法中使用泛型型別T;
* 4)與泛型類的定義一樣,此處T可以隨便寫為任意標識,常見的如T、E、K、V等形式的引數常用於表示泛型;
*/
public <T> T genInstance(Class<T> tClass) throws IllegalAccessException, InstantiationException {
T instance = tClass.newInstance();
return instance;
}
光看上面的例子可能依然會非常迷糊,我們再通過一個例子
public class ArrayList<E> implements List<E> {
/**
* 雖然在方法中使用了泛型,但是這並不是一個泛型方法。
* 這只是類中一個普通的成員方法,只不過他的返回值是在宣告泛型類已經宣告過的泛型。
* 所以在這個方法中才可以繼續使用 T 這個泛型。
*/
public E get(int index) {
//...
return elementData(index);
}
/**
* 將E改為T後,方法報錯,"cannot reslove symbol T"
* 因為在類的宣告中並未宣告泛型T,所以在使用E做形參和返回值型別時,編譯器會無法識別
*/
public T set(int index, T element) {
//...
return oldValue;
}
/**
* 這才是一個真正的泛型方法。
* 首先在public與返回值之間的<T>必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型T
* 這個T可以出現在這個泛型方法的任意位置.
*/
public <T> T[] toArray(T[] a) {
//...
return a;
}
}
4. 範型萬用字元
使用
?
代替範型標識
,標識操作資料型別可以為任何資料型別
java中我們都知道父類可以出現的地方,子類都是可以出現的,但是:
public static void method(List<Number> list){
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
method(list); // 此處報錯:List<java.lang.Number> cannot be applied to List<java.lang.Integer>
}
通過提示資訊我們可以看到List<Integer>
不能被看作為List<Number>
的子類。
由此可以看出:同一種泛型可以對應多個版本(因為引數型別是不確定的),不同版本的泛型類例項是不相容的。
我們可以將上面的方法改一下:
public static void method(List<?> list){
}
型別萬用字元一般是使用?
代替具體的型別實參,此處的?
和Number、String、Integer一樣都是一種實際的型別,可以把?
看成所有型別的父類,是一種真實的型別;
可以解決當具體型別不確定的時候,這個萬用字元就是?
;當不需要使用型別的具體功能只使用Object類中的功能,那麼可以用 ?
萬用字元來表未知型別。
5. 泛型上下邊界
在使用泛型的時候,我們還可以為傳入的泛型型別實參進行上下邊界的限制,如:型別實參只准傳入某種型別的父類或某種型別的子類。
public static void method(List<? extends Number> list){
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
method(intList); // Integer 是Number的子類
List<Double> doubleList = new ArrayList<>();
method(doubleList); // Double是Number的子類
List<String> strList = new ArrayList<>();
method(strList); // 此處報錯,String不是Number的子類
}
6. 範型擦除
Java在編譯期間,所有的泛型資訊都會被擦掉;
如在程式碼中定義List<Object>
和List<String>
等型別,在編譯後都會變成List,JVM看到的只是List,而由泛型附加的型別資訊對JVM是看不到的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法預知在執行時刻出現的型別轉換異常的情況;
擦除範型後只保留原始型別
原始型別 就是擦除去了泛型資訊,最後在位元組碼中的型別變數的真正型別,無論何時定義一個泛型,相應的原始型別都會被自動提供,型別變數擦除,並使用其限定型別(無限定的變數用Object)替換。
List<String> list1 = new ArrayList<>();
list1.add("abc");
List<Integer> list2 = new ArrayList<>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass()); // true
System.out.println(list1.getClass()); // class java.util.ArrayList
說明泛型型別String和Integer都被擦除掉了,只剩下原始型別List;
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1); //這樣呼叫 add 方法只能儲存整形,因為泛型型別的例項為 Integer
list.add("asd"); // 此處報錯
list.getClass().getMethod("add", Object.class).invoke(list, "asd"); // 通過反射獲取例項後可以新增成功
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
在程式中定義了一個ArrayList泛型型別例項化為Integer物件,如果直接呼叫add()方法,那麼只能儲存整數資料,不過當我們利用反射呼叫add()方法的時候,卻可以儲存字串,這說明了Integer泛型例項在編譯之後被擦除掉了,只保留了原始型別。
參考資料:
https://www.cnblogs.com/wuqinglong/p/9456193.html
https://blog.csdn.net/caihuangshi/article/details/51278793
https://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html
https://blog.csdn.net/caihuangshi/article/details/51278793