泛型的定義
泛型:參數(shù)化的類型晦闰。很簡單的一句話放祟,那么什么叫做“參數(shù)化的類型”呢?呻右。跪妥。。声滥。眉撵。
為什么需要泛型?
假設(shè)現(xiàn)在有這樣一個需求:把兩個整數(shù)進行相加并返回計算結(jié)果落塑。我們很輕松的就可以寫出如下代碼來完成此功能:
public int addInt(int x, int y) {
return x + y;
}
后來有一天業(yè)務(wù)拓展纽疟,需要支持浮點數(shù)進行相加并返回計算結(jié)果。于是我們可以新增一個新的如下方法來完成新需求:
public float addFloat(float x, float y) {
return x + y;
}
日子一天天的過去憾赁,又有了新的需求污朽,需要支持double類型的相加,那么我們依然可以依葫蘆畫瓢的再增加一個新的方法來完成double類型的數(shù)據(jù)相加龙考。誒蟆肆?等等矾睦。。颓芭。觀察上面的兩個方法除了參數(shù)的類型和返回值不同之外顷锰,其他的都相同(方法名是可以相同的,稱之為重載亡问,此處為了區(qū)分類型官紫,故寫作不同名稱)。于是為了解決這個問題州藕,java就引入了泛型機制束世,可以很好的解決重復(fù)代碼。
我們平常用的最多的數(shù)據(jù)結(jié)構(gòu)恐怕就是List了床玻。例如:
List list = new ArrayList();
list.add("element0");
list.add("element1");
list.add(100);
這段代碼無論在編寫階段還是運行階段都是不會報錯的毁涉。
for (int i = 0; i < list.size(); i++) {
String value = (String) list.get(i);
System.out.println("第" + (i + 1) + "個元素的值是: " + value);
}
可是當(dāng)我們需要用上面的代碼遍歷輸出集合中元素的時候,卻會報出ClassCastException锈死。這時因為最后添加的一個元素是 int 類型贫堰,強轉(zhuǎn)為 String 類型肯定是會報錯的。于是這里就體現(xiàn)出了泛型的第二個好處:安全待牵。于是我們經(jīng)常如下去創(chuàng)建一個List:
List<String> list = new ArrayList<>();
list.add("element0");
list.add("element1");
list.add(100); // 編譯器會提示此行代碼報錯
并且加了泛型之后在取值的時候也不需要強制類型轉(zhuǎn)換了其屏。
綜上所述:1.適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼。2.在編譯期間就發(fā)現(xiàn)數(shù)據(jù)類型安全問題缨该。這也就是我們?yōu)槭裁匆褂梅盒偷脑颉?/p>
泛型的使用
- 泛型類
public class GenericClass<T> {
private T data;
public GenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
- 泛型接口
public interface GenericInterface<T> {
T next();
}
關(guān)于泛型接口的實現(xiàn)有兩種:
public class GenericInterfaceImpl1 implements GenericInterface<String>{
@Override
public String next() {
return null;
}
}
public class GenericInterfaceImpl2<T> implements GenericInterface<T>{
@Override
public T next() {
return null;
}
}
這兩種方式的區(qū)別就是一個是在使用的時候才確定具體類型偎行,一個在聲明類的時候就確定了類型。
- 泛型方法
public <T> T genericMethod(T... t) {
return t[t.length / 2];
}
其中<T>是定義泛型方法所必須的贰拿,如果沒有的話蛤袒,即使這個方法帶有 T 妙真、或者 E 這種常見的泛型定義,那么它依然不是一個泛型方法。例如上面泛型類中的 getData() 或者 setData(T data),前面沒有<T>,它們依舊是普通的方法,只不過他們是定義在了泛型類中罷了溯壶。
類型變量的限定-用于方法上
public static <T extends Comparable> T min(T a, T b) {
if (a.compareTo(b) > 0) return a;else return b;
}
extends 關(guān)鍵字從面相對象上嚴格來說是叫派生又跛。那么 T extends Comparable 就可以理解為派生自 Comparable 這個接口的子類端幼。故下面可以使用Comparable 接口中的 compareTo() 方法摹迷。假如尖括號中只有一個 T鲫寄,那么參數(shù)a是無法調(diào)用compareTo()方法的地来。這就是所謂的類型變量的限定。
關(guān)于限定類型的使用有如下規(guī)則:
- 可以有多個限定類型熙掺,中間用 & 符號隔開
- 可以是類未斑,也可以是接口。如果是類的話需要放在第一個的位置
- 有且只能有一個類币绩,接口則沒有限制蜡秽。因為java是單繼承府阀,多實現(xiàn)。
下面是簡單示例:
public static <T extends View & Comparable & Serializable> T min(T a, T b) {
if (a.compareTo(b) > 0) return a;else return b;
}
此時傳入的參數(shù)需要滿足是 View 的子類芽突,且同時實現(xiàn)了Comparable 和 Serializable 接口才能使用试浙。否則無法通過編譯。
類型變量的限定-用于類上
public class GenericClass<T extends Comparable> {
private T data;
public GenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public T min(T t) {
if (this.data.compareTo(t) > 0) {
return t;
} else {
return this.data;
}
}
}
這里是把上面泛型類進行了一個改造寞蚌,這時泛型的具體類型只能是 Comparable 的實現(xiàn)類田巴。
泛型的約束和局限性
- 不能實例化類型變量
還是以上面的泛型類為例。這時我們給它添加一個無參的構(gòu)造方法睬澡。
public class GenericClass<T> {
private T data;
public GenericClass() {
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
假如我們想要在構(gòu)造方法中初始化 T 的實例是不允許的固额。編譯器會直接報錯:
-
靜態(tài)域或靜態(tài)方法不能引用類型變量
編譯器會直接報錯。這個問題的答案其實很簡單:泛型的具體類型是在創(chuàng)建出具體的泛型類的對象的時候才確定的煞聪,而靜態(tài)代碼的執(zhí)行是早于對象的創(chuàng)建的斗躏。那么虛擬機根本就不知道靜態(tài)的 T 是什么。但如果是靜態(tài)泛型方法則是可以的:
public static <E> void print(E e) {
System.out.println(e.toString());
}
- 基本數(shù)據(jù)類型不能作為泛型
GenericClass<double> genericClass1 = new GenericClass<>(); // 報錯 基本類型不可以
GenericClass<Double> genericClass2 = new GenericClass<>(); // 需要用其包裝類
- 不能使用 instanceof 關(guān)鍵字
通常我們需要判斷一個對象是不是某種類型的時候昔脯,都會用 instanceof 關(guān)鍵字來判斷啄糙。可是判斷某個對象是不是某個泛型類的類型時卻不可以云稚。例如:
GenericClass<Float> genericFloat = new GenericClass<>();
if (genericFloat instanceof GenericClass<Float>){ // 這一行編譯器會報錯
}
-
不能實例化泛型數(shù)組
可以聲明泛型數(shù)組隧饼,但是卻不能實例化。這是 java 語法規(guī)定静陈。是不是很奇葩燕雁?
-
泛型類不能繼承 Exception 或 Throwable
非常簡單粗暴,編譯器直接提示泛型類不能派生自 Throwable鲸拥。
-
不能捕獲泛型類對象
但是下面這種寫法確實允許的:
通配符
假設(shè)現(xiàn)在有兩個如下類拐格,且存在繼承關(guān)系:
public class Animal {
}
public class Dog extends Animal{
}
Dog 是 Animal 的子類,那么問題來了:
GenericClass<Animal> animalGeneric = new GenericClass<>();
GenericClass<Dog> dogGeneric = new GenericClass<>();
請問GenericClass<Animal> 和 GenericClass<Dog> 之間存在繼承關(guān)系嗎刑赶?答案顯然是否定的捏浊。但是泛型類可以繼承或擴展其他泛型類,例如 List 和 ArrayList 之間的關(guān)系:
public class GenericClassChild<T> extends GenericClass<T>{
}
再假設(shè)現(xiàn)在有如下一個方法:
public static <T> void set(GenericClass<Animal> genericClass) {
}
那么下面兩種調(diào)用可以嗎撞叨?
set(animalGeneric); // 1
set(dogGeneric); // 2
很顯然金踪,第一種是肯定可以的。第二種就不可以了牵敷。于是為了解決這種問題就有了通配符的概念胡岔。
-
? extends
現(xiàn)在有如下4個類,且有如下繼承關(guān)系:
假設(shè)現(xiàn)在有如下4個對象和對應(yīng)的打印方法:
GenericClass<Fruit> fruit = new GenericClass<>();
GenericClass<Apple> apple = new GenericClass<>();
GenericClass<RedApple> redApple = new GenericClass<>();
GenericClass<Orange> orange = new GenericClass<>();
public static <T> void printExtends(GenericClass<? extends Apple> genericClass) {
}
下面我們來看一下調(diào)用結(jié)果:
從上圖中我們可以得知 枷餐?extends 限定了傳入?yún)?shù)的上邊界姐军。那這樣使用有沒有什么限制呢?
可以看到當(dāng)我們嘗試著設(shè)置數(shù)據(jù)的時候是不允許的尖淘,當(dāng)我們要取數(shù)據(jù)的時候卻只可以用基類 Fruit 去接收奕锌。其實這也很好解釋。上面我們說了村生?extends 限定了上邊界惊暴,也就是說當(dāng)我們?nèi)?shù)據(jù)的時候,不管這個時候這個對象里有什么趁桃,但是一定是 Fruit 的子類辽话,根據(jù)多態(tài)的性質(zhì),可以用父類來接收子類卫病。至于設(shè)置數(shù)據(jù)的時候為什么不可以也就很好解釋了油啤。因為編譯器只知道傳入的是 Fruit 的子類,而至于傳入的具體是哪一個子類蟀苛,編譯器是不知道的益咬。至此我們可以總結(jié)一下:?extends 限定了傳入?yún)?shù)類型的上界帜平,用于安全的訪問數(shù)據(jù)
- 幽告?super
GenericClass<Fruit> fruit = new GenericClass<>();
GenericClass<Apple> apple = new GenericClass<>();
GenericClass<RedApple> redApple = new GenericClass<>();
GenericClass<Orange> orange = new GenericClass<>();
public static <T> void printSuper(GenericClass<? super Apple> genericClass) {
}
下面我們來看一下調(diào)用結(jié)果:
可以看到?super正好是限定了傳入?yún)?shù)的下邊界裆甩。下面我們來看一下有什么限制:
不是說 super 是限定了下邊界嗎冗锁?怎么在設(shè)置數(shù)據(jù)的時候父類 Fruit 反而不行了呢?而子類 RedApple 卻可以呢嗤栓?這是因為所有類的父類都是Object冻河,而編譯器無法確定傳入的是什么類型,但是 Apple 和 Apple 的子類是可以安全的轉(zhuǎn)型為Apple的茉帅,可以滿足最下邊界叨叙,所以可以安全傳入。這也就是為什么最后獲取的時候直接得到的是 Object 担敌,而不是 Fruit 摔敛。至此我們可以總結(jié)一下:?super 限定了傳入?yún)?shù)類型的下界全封,用于安全的設(shè)置數(shù)據(jù)