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官網給了一個圖幫助我們理解,底下的類可以賦值給箭頭指向的類。
例子
定義以下的內部類,其中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泛型