java泛型一二

bigminions發表於2019-01-22

java泛型可以被寫在方法上,介面上,類上面以及引數定義上。

為什麼使用泛型中,oracle提到相比於沒有泛型的程式碼,泛型提供了:

  • 編譯檢查
  • 消除強制轉型
  • 提高程式碼可讀性

本文試圖從容器角度來分析泛型,理解泛型的好處以及泛型的表現。我們將list,map之類的可以存放資料的物件成為容器。在沒有泛型出現之前,容器的型別是不可知的,只有開發者知道容器內可能放什麼東西,但是java對此並沒有限制,也即是開發者可以將任意的物件新增進容器內,反之從容器中取出的的元素也可以是任意物件。下面的情況很容易發生。

        List intList = new ArrayList();
        intList.add("element");
        Integer el = (Integer) intList.get(0);
複製程式碼

將約束交給開發者的大腦,對於後續維護以及開發都是潛在的危險,因為後續可能無法正確使用對應的容器,更不用說取出元素的時候要新增強制轉型來毀壞程式碼可讀性。在容器中,泛型提醒人們容器在執行時裝的是同一種類的物件。修改後的程式碼強壯性更高也更易讀,包括約束也浮上紙面。

        List<Integer> intList = new ArrayList();
        intList.add(1);
        Integer el = intList.get(0);
複製程式碼

有super和extend的泛型

在容器中,泛型使容器可以成為特定的容器,可以放置指定的類,比如List和List可以分別放置Number物件和Integer物件。但是這兩類容器是無法互相賦值的。儘管Integer繼承於Number,在java編譯器看來這是兩個不同的容器,假如想泛指多種容器,則需要使用super或extend來規定下限或上限。

上限-extend

以Number為例,List<? extend Number>泛指了List,List,List等所有繼承了Number類的子類構成的容器,包括Number在內,其中Number是上限,再往上的類以及其他的類不被泛指。因此從List<? extend Number>中取出的一定是個Number物件,可以使用Number類有的方法。List,List,List定義的物件可以賦值給List<? extend Number>定義的物件,但是無法反過來賦值。

下限-super

以Integer為例,List<? super Number>泛指了List,List,List等Integer繼承樹上的類構成的容器,此時Integer是下限,但是假如Integer有子類,也會包含在裡面。同樣,List,List,List定義的物件可以賦值給List<? super Number>定義的物件,但是無法反過來賦值。

oracle官網給了一個圖幫助我們理解,底下的類可以賦值給箭頭指向的類。

java泛型一二

例子

定義以下的內部類,其中Box類是容器,Fruit類是Food類的子類

    static class Box<T> {

        private T t;

        Box() {}
        Box(T t) {
            this.t = t;
        }

        T getT() {
            return t;
        }

        void setT(T t) {
            this.t = t;
        }
    }

    static class Food {}

    static class Fruit extends Food {}
複製程式碼

定義三個變數

    Object object = new Object();
    Food food = new Food();
    Fruit fruit = new Fruit();
複製程式碼

以下的程式碼illegal註釋代表編譯報錯:

    Box<? extends Food> box1= new Box<>(food);
    box1.setT(food);  //  illegal (case1)
    box1.setT(fruit);  //  illegal (case2)
    box1.setT(object);  //  illegal (case3)
    food = box1.getT();
    fruit = box1.getT();  //  illegal (case4)
複製程式碼

box1在執行時可能是一個food盒子,也可能是一個fruit盒子,這在編譯期是無法確定的,因此所有的set操作(case1、2、3)都被禁止,理由是可能放置進去錯誤的型別。而get操作則因為extend的限制取出的一定是個food,但不一定是fruit(case4)

    Box<? super Food> box2 = new Box<>(food);
    box2.setT(food);
    box2.setT(fruit);
    box2.setT(object);  //  illegal (case5)
    food = box2.getT();  //  illegal (case6)
    fruit = box2.getT();  //  illegal (case7)
複製程式碼

box2裡面,錯誤的型別不被允許set進去(case5),但是下限類可以被set進去,上面的第二和第三行並沒有報錯,在super限制的繼承樹上,food和fruit肯定可以強轉為繼承樹上的任意一個類。此處需要和box1做對比,case1中,food本身就是food,fruit也可以強轉為food,為什麼還被禁止set進去。因為food可以有多棵子樹,假如存在子樹meat,box1是放meat的,那麼此時case2肯定是報錯的,而同時,我們也無法得知case1裡面的food是屬於哪個類,其有可能是fruit強轉的,對於泛型而言,不確定的型別肯定也會報錯。

get操作因為無法super限制無法確切知道是哪個類而報錯(case6,case7)

例外情況

由於需要相容泛型出現前的情況,將一個不帶泛型的容器賦值給一個帶泛型的容器是被允許的,比如

        List list = new ArrayList();
        list.add("element");
        List<Integer> intList = list;
複製程式碼

參考 : oracle泛型

相關文章