1. 程式人生 > >Java範型學習筆記

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();
}
  1. 實現範型介面,未明確範型時:實現類後的範型標識不能省略
class Iter<T> implements Iterable<T> {
    @Override
    public Iterator<T> iterator() {
        return null;
    }
}
  1. 實現範型介面,明確範型時:實現類後的範型標識省略
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