【Java入門提高篇】Day15 Java泛型再探——泛型通配符及上下邊界


  上篇文章中介紹了泛型是什么,為什么要使用泛型以及如何使用泛型,相信大家對泛型有了一個基本的了解,本篇將繼續講解泛型的使用,讓你對泛型有一個更好的掌握和更深入的認識。

  上篇中介紹完泛型之后,是不是覺得泛型挺好用的?既消除了Object的不安全類型轉化,又可以很方便的進行類型對象的存取,但是,等一下,有沒有考慮到這樣的情況。

  我們先定義一個水果類:

public class Fruit {
    private String name;

    public Fruit(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

  然后再定義一個蘋果類:

public class Apple extends Fruit{
    public Apple(String name) {
        super(name);
    }
}

  接下來定義一個泛型容器:

public class GenericHolder<T> {
    private T obj;

    public GenericHolder(){}

    public GenericHolder(T obj){
        this.obj = obj;
    }

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}

  接下來開始我們的測試:

public class Test {

    /**
     * 吃水果
     * @param fruitHolder
     */
    public static void eatFruit(GenericHolder<Fruit> fruitHolder){
        System.out.println("我正在吃 " + fruitHolder.getObj().getName());
    }

    public static void main(String args[]){
        //這是一個貼了水果標簽的袋子
        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
        //這是一個貼了蘋果標簽的袋子
        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
        //這是一個水果
        Fruit fruit = new Fruit("水果");
        //這是一個蘋果
        Apple apple = new Apple("蘋果");

        //現在我們把水果放進去
        fruitHolder.setObj(fruit);
        //調用一下吃水果的方法
        eatFruit(fruitHolder);

        //貼了水果標簽的袋子放水果當然沒有問題
        //現在我們把水果的子類——蘋果放到這個袋子里看看
        fruitHolder.setObj(apple);
        //同樣是可以的,其實這時候會發生自動向上轉型,apple向上轉型為Fruit類型后再傳入fruitHolder中
        //但不能再將取出來的對象賦值給redApple了
        //因為袋子的標簽是水果,所以取出來的對象只能賦值給水果類的變量
        //無法通過編譯檢測 redApple = fruitHolder.getObj();
        eatFruit(fruitHolder);

        //放蘋果的標簽,自然只能放蘋果
        appHolder.setObj(apple);
        // 這時候無法把appHolder 傳入eatFruit
        // 因為GenericHolder<Fruit> 和 GenericHolder<Apple>是兩種不同的類型
        // eatFruit(appHolder);
    }
}

  運行結果:

我正在吃 水果
我正在吃 蘋果

  在這里,我們往eatFruit方法里傳入fuitHolder的時候,是可以正常編譯的,但是如果將appHolder傳入,就無法通過編譯了,因為作為參數時,GenericHolder<Fruit> 和 GenericHolder<Apple>是兩種不同的類型,所以無法通過編譯,那么問題來了,如果我想讓eatFruit方法能同時處理GenericHolder<Fruit> 和 GenericHolder<Apple>兩種類型怎么辦?而且這也是很合理的需求,畢竟Apple是Fruit的子類,能吃水果,為啥不能吃蘋果???如果要把這個方法重載一次,未免也有些小題大做了(而且事實上也無法通過編譯,具體原因之后會有說明)。 

  在代碼的邏輯里:

  • 蘋果 IS-A 水果
  • 裝蘋果的盤子 NOT-IS-A 裝水果的盤子

  這個時候,泛型的邊界符就有它的用武之地了。我們先來看效果:

public class Test {

    /**
     * 吃水果
     * @param fruitHolder
     */
    public static void eatFruit(GenericHolder<? extends Fruit> fruitHolder){
        System.out.println("我正在吃 " + fruitHolder.getObj().getName());
    }

    public static void main(String args[]){
        //這是一個貼了水果標簽的袋子
        GenericHolder<Fruit> fruitHolder = new GenericHolder<Fruit>();
        //這是一個貼了蘋果標簽的袋子
        GenericHolder<Apple> appHolder = new GenericHolder<Apple>();
        //這是一個水果
        Fruit fruit = new Fruit("水果");
        //這是一個蘋果
        Apple apple = new Apple("蘋果");

        //現在我們把水果放進去
        fruitHolder.setObj(fruit);
        //調用一下吃水果的方法
        eatFruit(fruitHolder);

        //放蘋果的標簽,自然只能放蘋果
        appHolder.setObj(apple);
        // 這時候可以順利把appHolder 傳入eatFruit
        eatFruit(appHolder);
    }
}

  運行結果:

我正在吃 水果
我正在吃 蘋果

  這里我們只是使用了一點小小的魔法,把參數類型改成了GenericHolder<? extends Fruit>,這樣就能將 GenericHolder<Apple>類型的參數順利傳入了,怎么樣?很好用吧,這就是泛型的邊界符,用<? extends Fruit>的形式表示。邊界符的意思,自然就是定義一個邊界,這里用?表示傳入的泛型類型不是固定類型,而是符合規則范圍的所有類型,用extends關鍵字定義了一個上邊界,也就是說這里的?可以代表任何繼承於Fruit的類型,你也許會問,為什么是上邊界,好問題,一圖勝千言:

  從這個圖可以很好的看出這個“上邊界”的概念了吧。有上邊界,自然有下邊界,泛型里使用形如<? super Fruit>的方式使用下邊界,此時,?只能代表Fruit及其父類。

  (這兩個圖是摳過來的,不要罵我懶。)

  這兩種方式基本上解決了我們之前的問題,但是同時,也有一定的限制。

   1.上界<? extends T>不能往里存,只能往外取

  不要太疑惑,其實很好理解,因為編譯器只知道容器里的是Fruit或者Fruit的子類,但不知道它具體是什么類型,所以存的時候,無法判斷是否要存入的數據的類型與容器種的類型一致,所以會拒絕set操作。

  2.下界<? super T>往外取只能賦值給Object變量,不影響往里存

  因為編譯器只知道它是Fruit或者它的父類,這樣實際上是放松了類型限制,Fruit的父類一直到Object類型的對象都可以往里存,但是取的時候,就只能當成Object對象使用了。

  所以如果需要經常往外讀,則使用<? extends T>,如果需要經常往外取,則使用<? super T>。

  至此,本篇講解完畢,歡迎大家繼續關注!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM