首頁 > 軟體

一文帶你搞懂Java中的泛型和萬用字元

2022-09-06 18:01:27

概述

泛型機制在專案中一直都在使用,比如在集合中ArrayList<String, String>, Map<String,String>等,不僅如此,很多原始碼中都用到了泛型機制,所以深入學習瞭解泛型相關機制對於原始碼閱讀以及自己程式碼編寫有很大的幫助。但是裡面很多的機制和特性一直沒有明白,特別是萬用字元這塊,對於萬用字元上界、下界每次用每次百度,經常忘記,這次我就做一個總結,加深自己的理解。

泛型介紹和使用

泛型在類定義時不會設定類中的屬性或方法引數的具體型別,而是在類使用時(建立物件)再進行型別的定義。會在編譯期檢查型別是否錯誤, 保證程式的可讀性和安全性。

泛型定義根據實際情況可以分為泛型類和泛型方法:

泛型類

public class Point<T, U> {

    private T pointX;

    private U pintY;

    public Point(T pointX, U pintY) {
        this.pointX = pointX;
        this.pintY = pintY;
    }

    public void showPoint() {
        System.out.println(pointX);
        System.out.println(pintY);
    }
}
  • 類中引入型別變數,型別變數指的T, U這些,用尖括號<>括起來, 跟在類名後面。
  • 多個型別變數可以用逗號分隔。
  • 在類中的方法和返回值等地方可以使用型別變數。
  • 型別變數採用大寫形式,要求簡短,一般E表示集合的元素型別,K和V表示key和value等。
  • 泛型類使用:Point<Integer, Double>

泛型方法

public class FxMethod {

    public static <T> T getMiddleNumber(T ... numbers) {
        return null;
    }

    public <T, U> void showNumber(T t, U u) {
        System.out.println("t = " + t);
        System.out.println("u = " + u);;
    }
}
  • 方法中引入型別變數,在返回型別前新增<>, 中間放置型別變數,多個型別變數用逗號分隔。
  • 在方法的引數和返回值等位置可以使用型別變數。
  • 泛型方法使用:Integer result = FxMethod.getMiddleNumber(2, 3) 或者 Integer result = FxMethod.<Integer>getMiddleNumber(2, 3)

型別變數的限定

前面講解了泛型一般定義的兩種方式,其中的型別變數沒有任何限定, 這樣在導致一方面在定義泛型的時候無法使用一些API, 需要強轉,另一方面在使用的時候也容易出錯,那麼如何給型別變數新增限定呢?

  • 只有通過extends關鍵字限定,不能通過super關鍵字。
  • 加了限定以後,就可以直接使用限定類相關的API。
  • 多個限定之間用&符號,比如T extends Number & Comparable
  • 使用泛型時,只能傳入相應限定的類,比如傳入Point<String, String> 就會報編譯錯誤。

萬用字元使用

泛型的引入的確解決了很大問題,那它是完美的嗎?

class AnimalWrapper<T extends Animal> {
    private T animal;

    AnimalWrapper(T animal) {
        this.animal = animal;
    }

    public void eat() {
       animal.eat();
    }

}

class Animal {
    private String name;

    public void eat() {
        System.out.println("animal eat -----");
    }
}

class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println(" cat eat -----");
    }
}

定義一個AnimalWrapper,泛型變數中限定為Animal,如果是下面的測試類,會怎麼樣呢?

會編譯報錯,因為AnimalWrapper並不是AnimalWrapper的子類,不能直接傳入。為了解決個問題,我們引入了萬用字元,萬用字元一般是在方法中或者泛型類使用中用到。

AnimalWrapper可以作為AnimalWrapper<?extends Animal>的子型別,這就是利用萬用字元帶來的好處。

  • 統配符使用在集合或者方法的引數返回值中。
  • 萬用字元可以分為無邊界萬用字元、上邊界萬用字元和下邊界萬用字元。

無邊界萬用字元

萬用字元無邊界,可以傳入任何型別,沒有限制,相當於Object.

基本語法:

<?>

例子:

public static void printList1(List<?> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        printList1(list);  // ok
        List<String> list2 = new ArrayList<>();
        list2.add("1");
        printList1(list2); // ok

        
        List<?> list3 = list;
        // get只能用Object接受, 
        Object o = list3.get(0);
        list3.add(5);   // compile error
        list3.add(new Object()); // compile error
    }

小結:

  • 無邊界萬用字元相當於Object,任何型別都可以傳入,比如 List<Integer> list, List<String> list2
  • 由於?無法確定是哪種型別,所以只能使用Object型別的變數接收, 比如例子中的: Object o = list3.get(0);
  • 如果是無邊界萬用字元對應的集合型別,不能新增任何元素。因為無法確定集合存放資料的型別,鬼知道我們要放什麼型別才合適啊。

萬用字元上界

萬用字元上界,可以限制傳入的型別必須是上界這個類或者是這個類的子類。

基本語法:

<? extends 上界> 
<? extends Number>//可以傳入的實參型別是Number或者Number的子類

例子:

public static void printList1(List<? extends Number> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }


    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        printList1(list);  // ok
        List<Double> list1 = new ArrayList<>();
        list1.add(1.0D);
        printList1(list1);  // ok
        List<String> list2 = new ArrayList<>();
        list2.add("1");
        printList1(list2); // compile error


        List<? extends Number> list3 = list;
        // get能用上界
        Number o = list3.get(0);
        // 不能add
        list3.add(5);   // compile error
        list3.add(new Object()); // compile error

    }

小結:

  • 萬用字元上界? extends A, 表明所有的是A的類或者子型別可以傳入,比如本例中的Integer和Double都是Number的子類,String不是。
  • 萬用字元上界? extends A,確定了型別是A或者是A的子類,那麼向集合容器get獲取資料,肯定是它的上界類A,因為其他放的類都是A的子類,比如例子中的 Number o = list3.get(0);
  • 如果向萬用字元上界集合中新增元素時,會失敗。 List<? extends A>, 說明容器可以容納的是A或者A的子類,但A的子類有很多,不確定放哪個,為了安全性,就直接不讓你add,比如例子中的list3.add(5); ,5雖然是Number的子類,依然不能add。

萬用字元下界

萬用字元下界,可以限制傳入的型別必須是這個類或者是這個類的父類別。

基本語法:

<? super 下界> 
<? super Integer>//代表 可以傳入的實參的型別是Integer或者Integer的父類別型別

例子:

public static void printList1(List<? super Integer> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }


    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        printList1(list);  // ok
        List<Double> list1 = new ArrayList<>();
        list1.add(1.0D);
        printList1(list1);  // compile error
        List<String> list2 = new ArrayList<>();
        list2.add("1");
        printList1(list2); // compile error


        List<? super Integer> list3 = list;
        // 不能用下界接收
        Integer o = list3.get(0); // compile error
        // 能add
        list3.add(5);   // ok
        list3.add(new Number(5)); // compile error

    }
  • 萬用字元上界? super A, 表明所有的是A的類或者A的父類別可以傳入。
  • 萬用字元上界? super A,確定了型別是A或者是A的父類別,那麼向集合容器get獲取資料,無法確定是A還是A的某個父類別,所以不能get, Integer o = list3.get(0); // compile error,比如例子中用Integer接收,萬一list3中放的是Object型別,就涼涼了。
  • 如果向萬用字元下界集合中新增元素時,只能新增下屆類的子類。比如例子中的:list3.add(5), list3的萬用字元是<? super Integer>,說明該集合存放的是Integer或者Integer的父類別,我只要向容器中放Integer和它的子類都是成立的。

到此這篇關於一文帶你搞懂Java中的泛型和萬用字元的文章就介紹到這了,更多相關Java泛型 萬用字元內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


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