首頁 > 軟體

Java知識梳理之泛型用法詳解

2022-08-08 18:00:40

泛型

背景:

從JDK 5.0以後,Java引入了“引數化型別(Parameterized type)”的概念,允許我們在建立集合時再指定集合元素的型別,正如:List ,這表明該List只能儲存字串型別的物件。

作用

解決元素儲存的安全性問題,好比商品、藥品標籤,不會弄錯。

解決獲取資料元素時,需要型別強制轉換的問題,好比不用每回拿商品、藥品都要辨別。

@Test
public void test1(){
    ArrayList list = new ArrayList();
    //需求:存放學生的成績
    list.add(78);
    list.add(76);
    list.add(89);
    list.add(88);
    //問題一:型別不安全
    //        list.add("Tom");

    for(Object score : list){
        //問題二:強轉時,可能出現ClassCastException
        int stuScore = (Integer) score;
    
        System.out.println(stuScore);
    }
}

//在集合中使用泛型,以ArrayList為例
@Test
public void test1(){
    ArrayList<String> list = new ArrayList<>();
    list.add("AAA");
    list.add("BBB");
    list.add("FFF");
    list.add("EEE");
    list.add("CCC");
    //遍歷方式一:
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    System.out.println("-------------");
    //便利方式二:
    for (String str:
         list) {
        System.out.println(str);
    }
}

集合中泛型

① 集合介面或集合類在JDK 5.0時都修改為帶泛型的結構。

② 在範例化集合類時,可以指明具體的泛型型別

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

比如:add(E e) —>範例化以後:add(Integer e)

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

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

說明

1.泛型類可能有多個引數,此時應將多個引數一起放在尖括號內。比如<E1,E2,E3>

2.泛型類的構造器如下: public GenericClass(){}

而下面是錯誤的: public GenericClass<E>{}

3.範例化後,操作原來泛型位置的結構必須與指定的泛型型別一致。

4.泛型不同的參照不能相互賦值。

儘管在編譯時 ArrayList和ArrayList是兩種型別,但是,在執行時只有一個ArrayList被載入到JVM中。

5.泛型如果不指定,將被擦除,泛型對應的型別均按照Object處理,但不等價於Object。

建議:泛型要使用一路都用。要不用,一路都不要用。

6.如果泛型結構是一個介面或抽象類,則不可建立泛型類的物件。

7.JDK 7.0,泛型的簡化操作: ArrayList<Fruit>first= new ArrayList<>();(型別推斷)

8.泛型的指定中不能使用基本資料型別,可以使用包裝類替換。

9.在類/介面上宣告的泛型,在本類或本介面中即代表某種型別,可以作為非靜態屬性的型別、非靜態方法的引數型別、非靜態方法的返回值型別。但在靜態方法中不能使用類的泛型。

10.異常類不能是泛型的。

11.不能使用 new E[]。但是可以:E[] elements= (E[])new Object[capacity];

參考:ArrayList原始碼中宣告:Object[] elementData,而非泛型引數型別陣列。

12.父類別有泛型,子類可以選擇保留泛型也可以選擇指定泛型型別

子類不保留父類別的泛型:按需實現

  • 沒有型別—擦除
  • 具體型別

子類保留父類別的泛型:泛型子類

  • 全部保留
  • 部分保留

結論:子類必須是“富二代”,子類除了指定或保留父類別的泛型,還可以增加自己的泛型

自定義泛型

泛型類、泛型介面、泛型方法

泛型的宣告

interface List<T> 和 class GenTest<K,V> 其中,T,K,V,不代表值,而是表示型別。這裡使用任意字母都可以。

常用T表示,是Type的縮寫。

泛型的範例化

一定要在類名後面指定型別引數的值(型別)。如:

List<String> strList =new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();

T只能是類,不能用基本資料型別填充。但可以使用包裝類填充

把一個集合中的內容限制為一個特定的資料型別,這就是 generics背後的核心思想

//JDK 5.0以前
Comparable c = new Date();
System.out.println(c.comparaTo("red");
                   
//JDK 5.0以後
Comparable <Date> c = new Date();
System.out.println(c.comparaTo("red");                   

自定義泛型類

程式碼範例:

/**
 * 自定義泛型類Order
 */
class Order<T> {
    private String orderName;
    private int orderId;
    //使用T型別定義變數
    private T orderT;

    public Order() {
    }
    //使用T型別定義構造器
    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //這個不是泛型方法
    public T getOrderT() {
        return orderT;
    }
    //這個不是泛型方法
    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }
    //這個不是泛型方法
    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + ''' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
//    //靜態方法中不能使用類的泛型。
//    public static void show(T orderT){
//        System.out.println(orderT);
//    }

//    //try-catch中不能是泛型的。
//    public void show(){
//        try {
//
//        }catch (T t){
//
//        }
//    }

    //泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
    //換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
    //泛型方法,可以宣告為靜態的。
    // 原因:泛型引數是在呼叫方法時確定的。並非在範例化類時確定。
    public static <E> List<E> copyFromArryToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e :
                list) {
            list.add(e);
        }
        return list;
    }
}

自定義泛型介面

程式碼範例:

/**
 * 自定義泛型介面
 */
public interface DemoInterface <T> {
    void show();
    int size();
}

//實現泛型介面
public class Demo implements DemoInterface {
    @Override
    public void show() {
        System.out.println("hello");
    }

    @Override
    public int size() {
        return 0;
    }
}

@Test
//測試泛型介面
public void test3(){
    Demo demo = new Demo();
    demo.show();
}

自定義泛型方法

方法,也可以被泛型化,不管此時定義在其中的類是不是泛型類。在泛型方法中可以定義泛型引數,此時,引數的型別就是傳入資料的型別。

泛型方法的格式: [存取許可權]<泛型>返回型別 方法名(泛型標識 引數名稱])丟擲的異常

泛型方法宣告泛型時也可以指定上限

泛型方法宣告泛型時也可以指定上限

程式碼範例:

//泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
//換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
//泛型方法,可以宣告為靜態的。
// 原因:泛型引數是在呼叫方法時確定的。並非在範例化類時確定。
public static <E> List<E> copyFromArryToList(E[] arr) {
    ArrayList<E> list = new ArrayList<>();
    for (E e :
         list) {
        list.add(e);
    }
    return list;
}

萬用字元

1.萬用字元的使用

使用型別萬用字元:?

比如:List<?>,Map<?,?>

List<?> 是 List<String>、List<Object> 等各種泛型 List 的父類別。

讀取 List<?> 的物件list中的元素時,永遠是安全的,因為不管list的真實型別是什麼,它包含的都是Object

寫入list中的元素時,不可以。因為我們不知道c的元素型別,我們不能向其中新增物件。 除了新增null之外。

說明:

將任意元素加入到其中不是型別安全的

Collection<?> c = new ArrayList<String>()

c.add(new Object());//編譯時錯誤

因為我們不知道c的元素型別,我們不能向其中新增物件。add 方法有型別引數 E 作為集合的元素型別。我們傳給add的任何引數都必須是一個已知型別的子類。因為我們不知道那是什麼型別,所以我們無法傳任何東西進去。

唯一的例外的是 null,它是所有型別的成員。

我們可以呼叫 get() 方法並使用其返回值。返回值是一個未知的型別,但是我們知道,它總是一個Object。

程式碼範例:

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;

    list = list1;
    list = list2;
    //編譯通過
    //        print(list1);
    //        print(list2);

    //
    List<String> list3 = new ArrayList<>();
    list3.add("AA");
    list3.add("BB");
    list3.add("CC");
    list = list3;
    //新增(寫入):對於List<?>就不能向其內部新增資料。
    //除了新增null之外。
    //        list.add("DD");
    //        list.add('?');

    list.add(null);

    //獲取(讀取):允許讀取資料,讀取的資料型別為Object。
    Object o = list.get(0);
    System.out.println(o);
}

public void print(List<?> list){
    Iterator<?> iterator = list.iterator();
    while(iterator.hasNext()){
        Object obj = iterator.next();
        System.out.println(obj);
    }
}

2.注意點

//注意點1:編譯錯誤:不能用在泛型方法宣告上,返回值型別前面<>不能使用?
public static <?> void test(ArrayList<?> list){
    
}

//注意點2:編譯錯誤:不能用在泛型類的宣告上
class GenericTypeClass<?>{
    
}

//注意點3:編譯錯誤:不能用在建立物件上,右邊屬於建立集合物件
ArrayList<> list2 new ArrayList<?>();

3.有限制的萬用字元

1.<?>:允許所有泛型的參照呼叫

2.萬用字元指定上限

上限 extends:使用時指定的型別必須是繼承某個類,或者實現某個介面,即 <=

3.萬用字元指定下限

下限 super:使用時指定的型別不能小於操作的類,即 >=

4.舉例:

<?extends Number>(無窮小, Number]

只允許泛型為Number及Number子類的參照呼叫

<?super Number>[Number,無窮大)

只允許泛型為Number及Number父類別的參照呼叫

<? extends Comparable>

只允許泛型為實現 Comparable介面的實現類的參照呼叫

程式碼範例:

@Test
public void test4(){

    List<? extends Person> list1 = null;
    List<? super Person> list2 = null;

    List<Student> list3 = new ArrayList<Student>();
    List<Person> list4 = new ArrayList<Person>();
    List<Object> list5 = new ArrayList<Object>();

    list1 = list3;
    list1 = list4;
    //        list1 = list5;

    //        list2 = list3;
    list2 = list4;
    list2 = list5;

    //讀取資料:
    list1 = list3;
    Person p = list1.get(0);
    //編譯不通過
    //Student s = list1.get(0);

    list2 = list4;
    Object obj = list2.get(0);
    編譯不通過
    //        Person obj = list2.get(0);

    //寫入資料:
    //編譯不通過
    //        list1.add(new Student());

    //編譯通過
    list2.add(new Person());
    list2.add(new Student());

}

到此這篇關於Java知識梳理之泛型用法詳解的文章就介紹到這了,更多相關Java泛型內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com