Java(7)泛型

Javalove劉志先發表於2020-11-22

一、泛型概述

1、什麼是泛型

  • 泛型就是標籤,加了泛型,就相當於加了標籤,這個容器就只能放這一類物品。

  • 把元素的型別設引數,這個型別引數叫做泛型。Collection<E>,List<E>,ArrayList<E> 這個<E>就是型別引數,即泛型。

  • 所謂泛型,就是允許在定義類、介面時通過一個標識表示類中某個屬性的型別或者是某個方法的返回值及引數型別。這個型別引數將在使用時(例如, 繼承或實現這個介面,用這個型別宣告變數 、建立物件時)確定(即傳入實 際的型別引數,也稱為型別實參)。

  • JDK1.5時引入。

2、為什麼用泛型

  • 儲存時:編譯時進行型別檢查來保證,存放的都是型別一樣的資料,更安全。
  • 獲取時:獲取的都是型別一樣的資料,無序強轉

3、在集合中使用泛型

  • ① 集合介面或集合類在jdk5.0時都修改為帶泛型的結構。
  • ② 在例項化集合類時,可以指明具體的泛型型別。

  • ③ 指明完以後,在集合類或介面中凡是定義類或介面時,內部結構(比如:方法、構造器、屬性等)使用到類的泛型的位置,都指定為例項化的泛型型別。

    比如:add(E e) --->例項化以後:add(Integer e)。

  • ④ 注意點:泛型的型別必須是類,不能是基本資料型別。需要用到基本資料型別的位置,拿包裝類替換。

  • ⑤ 如果例項化時,沒有指明泛型的型別。預設型別為java.lang.Object型別。

  • ⑥在JDK7中新特性:型別推斷

    • List<String> list = new List<>();後面的泛型可以省略

二、自定義泛型結構

1、泛型類、介面

  • 自定義泛型類程式碼實現:核心思想就是把T當做一個某某型別的引數,只不過需要在尖括號標明
/**
 * 自定義泛型類
 */
public class MyGeneric<T> {
    String name;
    int age;
    T decs;//可以把他當做一個類來看,但是並不是類,而是引數

    public MyGeneric() {
    }

    //帶參構造器
    public MyGeneric(String name, int age, T decs) {
        this.name = name;
        this.age = age;
        this.decs = decs;
    }

    //get()方法
    public T getDecs() {
        return decs;
    }

    //set()方法
    public void setDecs(T decs) {
        this.decs = decs;
    }

    //toString()方法
    @Override
    public String toString() {
        return "MyGeneric{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", decs=" + decs +
                '}';
    }
}
  • 子類繼承父類時,泛型的情況
    • 說明:子類是“富二代”,除了指定或保留父類的泛型,還可以增加自己的泛型。
    • 情況一:子類不保留父類的泛型
      • 父類沒有型別,擦除,型別按照Object處理
      • 父類是具體型別
    • 情況二:子類保留父類的型別
      • 全部保留
      • 部分保留
    • 程式碼示例
class Father<T1, T2>{
}

//子類不保留父類的泛型
//1)沒有型別,擦除--->這時,子類不是泛型
class Son1 extends Father{//等價於class Son extends Father<Object,Object>{
}

//2)具體型別--->這時,子類不是泛型
class son2 extends Father<Integer, String>{
}

//子類保留父類的泛型
//1)全部保留
class Son3<T1, T2> extends Father<T1, T2>{
}
//2)部分保留
class Son4<T2> extends Father<Integer, T2>{
}

//當然子類也可以加上自己的新的泛型型別

  • 注意點

    • 泛型類裡的泛型是以引數的形式存在的。

    • 可以有多個引數:<T1,T2,T3>

    • 宣告構造器時

      • 空參構造器和普通的一樣 public MyGeneric() {}
      • 帶參構造器體現在引數上 public MyGeneric(String name, int age, T decs) {
    • 泛型不同的引用不能相互賦值

      ArrayList<String> list1 = new ArrayList<>();
      ArrayList<Integer> list2 = new ArrayList<>();
              
      list1 = list2;//這樣是錯誤的!
      
    • 泛型如果不指定,就會被擦除,泛型對應的型別按照Object處理,但不等價於Object。

    • 在類/介面上宣告的泛型,在本類或本介面中即代表某種型別,可以作為非靜態 屬性的型別、非靜態方法的引數型別、非靜態方法的返回值型別。但在靜態方法中不能使用類的泛型。(因為,靜態方法隨著類的載入而載入,而泛型是在例項化的時候指定,晚於靜態方法出生)

    • 異常類不能宣告為泛型的。

    • 如果想在構造器中初始化一個T型別的陣列,要寫作:

      T[] t = (T[])new Object[capacity]

2、泛型方法

  • 理解
    • 在方法中出現了泛型的結構,就是泛型方法。
    • 泛型方法中的泛型引數與類的泛型引數沒有關係。也就是說:泛型方法所屬的類是不是泛型無所謂。
    • 可以宣告為靜態的,因為泛型引數是在呼叫方法時確定的,而不是來載入時。
  • 程式碼實現一個泛型方法
public static <E> List<E> copyFromArrayToList(E[] arr){
    //這裡的第一個<E>是為了不讓編譯器把E當做是一個我們自定義的類
    ArrayList<E> list = new ArrayList<>();
    
    for(E e : arr){
        list.add(e);
    }
    
    return list;
}

三、舉例泛型類和泛型方法的使用場景

1、泛型類舉例:

連線資料庫時,運算元據庫中的表的記錄

  • DAO類:定義了資料庫不同表的一些共性操作的類

    public class DAO<T> {}

  • CustomerDAO:定義了專門操作Customer表的類

    public class CustomerDAO extends DAO<Customer>{}

2、泛型方法舉例

//泛型方法舉例
    /*
    返回的東西是不確定的,比如
        需求1:表中有多少條記錄
        需求2:獲取工資最大值
     */
    public <E> E getSomething(){
        return null;
    }

四、泛型在繼承上的體現

  • 首先,我們知道在多型下,子類的物件可以賦給父類的引用。
  • 但是,在泛型情況下,不再存在子父類的關係。例如,String是Object的子類,但是List<String >並不是List<Object> 的子類。同理在方法引數中也一樣適用。這樣就導致一個問題,同一功能的方法要過載很多次。
  • 區別,類B是類A的子類,那麼B<T>仍然是A<T>的子類。

五、萬用字元

1、萬用字元

萬用字元用 ?來表示,含義為:類A是類B的父類,G<A> G<B>並不存在什麼關係,二者的共同父類是:G<?>。這樣就不必過載那麼多功能相同的方法了。

2、新增萬用字元後資料的寫入何人讀出

  • 寫入:對於List<?>,不能向其中新增資料,只能新增null;
  • 讀出:可以讀取,讀取出的資料為Object型別

3、有限制條件的萬用字元

  • 首先,萬用字元?可以的範圍可以理解為: (無窮小,無窮大)
  • ? extends A的範圍就可以理解為:小於等於,即(無窮小,A]
  • ? super A的範圍就可以理解為:大於等於,即[A,無窮大)

相關文章