Java-泛型

ML李嘉圖發表於2022-03-03

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繼承List的關係

就像請倆技師一個給兒子捏腳一個給爸爸捏腳,雖然兒子和爸爸有繼承關係,但是兩個技師沒有任何關係(假設)

萬用字元限制條件

我們前面看到了使用?萬用字元可以傳入任何型別,但是使用Object作為形參一樣可以完成,所以為了安全性,萬用字元還提供了一系列限制條件

<? extends xxx><? super xxx>

先來說<? extends xxx> 可以匹配繼承於xxx或者xxx型別 小於等於該類的型別

我們使用常用的List來測試,建立3個類,類中什麼都沒有,僅是來測試萬用字元

Animal動物類為父類->Cat貓類繼承Animal類-> XiaoHuaCat小花貓類繼承Cat類

@Test
public void test1() {
    //設定萬用字元限制條件
    List<? extends Cat> list = new ArrayList<>();
    List<Cat> cats = new ArrayList<>();
    List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();

    list = cats;
    list = xiaoHuaCats;
    //這條賦值語句報錯,因為不是繼承Cat或Cat型別
    list = animals;
}

那麼它get方法返回的型別為Cat類,因為這已經是在這個集合中最大的父類了,它在這個集合中不可能再有父類了

再來說<? extends xxx> 可以匹配xxx繼承的類型別或者xxx型別 大於等於該類的型別

@Test
public void test1() {
    List<? super Cat> list = new ArrayList<>();
    List<Cat> cats = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>();

    list = cats;
    list = animals;
    //這條語句會報錯,因為最小的型別就是Cat類了,而XiaoHuaCat是繼承於Cat的
    list = xiaoHuaCats;
}

它的get方法返回型別為Object ,因為無論哪個類,它的祖先類一定是Obejct類,因為父類物件引用子類物件是允許的,所以get的是所有類的父類

本文僅個人理解,如果有不對的地方歡迎評論指出或私信,謝謝٩(๑>◡<๑)۶

相關文章