
在我們的實際工作中 泛型(Generics) 是無處不在的,我們也寫過不少,看到的更多,如,源碼、開源框架... 隨處可見,但是,我們真正理解泛型嗎?理解多少呢?例如:Box 、Box<Object> 、Box<?> 、Box<T> 、Box<? extends T> 、Box<? super T> 之間的區(qū)別是什么?本篇文章將會對 泛型(Generics) 進行全面的解析,讓我們對泛型有更深入的理解。
以下是我的博客和GitHub地址:
博客地址:https://h.lishaoy.net
GitHub地址:https://github.com/persilee
本篇文章的示例代碼放在 Github 上,所有知識點,如圖:

Lucy 喜歡吃??(為什么要使用泛型)
首先,通過一個盤子裝水果小故事來打開我們的泛型探索之旅(我們?yōu)槭裁匆褂梅盒停?,故事場景如下?/p>
Lucy 到 James 家做客,James 需要招待客人,且知道 Lucy 喜歡吃橘子??,于是使用水果盤裝滿了??來招待客人
這個場景怎么用代碼表現(xiàn)呢,我們來新建幾個類,如下:
Fruit:水果類
package entity;
public class Fruit {
@Override
public String toString() {
return "This is Fruit";
}
}
Apple:蘋果類,繼承水果類
package entity;
public class Apple extends Fruit {
@Override
public String toString() {
return " Apple ??";
}
}
Orange:橘子類,繼承水果類
package entity;
public class Orange extends Fruit {
@Override
public String toString() {
return " Orange ??";
}
}
Plate:水果盤接口
package entity;
public interface Plate<T> {
public void set(T t);
public T get();
}
FruitPlate:水果盤類,實現(xiàn)水果盤接口
package entity;
import java.util.ArrayList;
import java.util.List;
public class FruitPlate implements Plate {
private List items = new ArrayList(6);
@Override
public void set(Object o) {
items.add(o);
}
@Override
public Fruit get() {
int index = items.size() - 1;
if(index >= 0) return (Fruit) items.get(index);
return null;
}
}
AiFruitPlate:智能水果盤,實現(xiàn)水果盤接口
package entity;
import java.util.ArrayList;
import java.util.List;
/**
* 使用泛型類定義
* @param <T>
*/
public class AiFruitPlate<T> implements Plate<T> {
private List<T> fruits = new ArrayList<T>(6);
@Override
public void set(T t) {
fruits.add(t);
}
@Override
public T get() {
int index = fruits.size() - 1;
if(index >= 0) return fruits.get(index);
return null;
}
}
Person:人類
package entity;
public class Person {
}
Lucy:Lucy類,繼承 Person 類,她擁有吃橘子的能力 eat
import entity.Orange;
import entity.Person;
public class Lucy extends Person {
public void eat(Orange orange) {
System.out.println("Lucy like eat" + orange);
}
}
James:James類,繼承 Person 類,他擁有獲取水果盤的能力 getAiFruitPlate
import entity.*;
public class James extends Person {
public FruitPlate getPlate() {
return new FruitPlate();
}
public AiFruitPlate getAiFruitPlate() {
return new AiFruitPlate();
}
public void addFruit(FruitPlate fruitPlate, Fruit fruit) {
fruitPlate.set(fruit);
}
public void add(AiFruitPlate<Orange> aiFruitPlate, Orange orange) {
aiFruitPlate.set(orange);
}
}
Scenario:測試類
import entity.*;
public class Scenario {
public static void main(String[] args) {
scenario1();
scenario2();
}
//沒有使用泛型
private static void scenario1() {
James james = new James();
Lucy lucy = new Lucy();
FruitPlate fruitPlate = james.getPlate(); // James 拿出水果盤
james.addFruit(fruitPlate,new Orange()); // James 往水果盤里裝橘子
lucy.eat((Orange) fruitPlate.get()); // 需要轉(zhuǎn)型為 Orange
}
//使用了泛型
private static void scenario2() {
James james = new James();
Lucy lucy = new Lucy();
AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate(); // James 拿出智能水果盤(知道你需要裝橘子)
james.add(aiFruitPlate, new Orange()); // James 往水果盤里裝橘子(如果,裝的不是橘子會提醒)
lucy.eat(aiFruitPlate.get()); // 不需要轉(zhuǎn)型
}
}
運行結(jié)果,如下:
Lucy like eat Orange ??
Lucy like eat Orange ??
Process finished with exit code 0
我們可以很明顯的看出,使用了泛型之后,不需要類型轉(zhuǎn)換,如果,我們把 scenario1() 方法,稍微改下,如下:
private static void scenario1() {
James james = new James();
Lucy lucy = new Lucy();
FruitPlate fruitPlate = james.getPlate();
james.addFruit(fruitPlate,new Apple()); //new Orange() 改成 new Apple()
lucy.eat((Orange) fruitPlate.get());
}
編譯器不會提示有問題,但是運行之后報錯,如下:
Exception in thread "main" java.lang.ClassCastException: entity.Apple cannot be cast to entity.Orange
at Scenario.scenario1(Scenario.java:21)
at Scenario.main(Scenario.java:7)
Process finished with exit code 1
而,我們把 scenario2() (使用了泛型)做出同樣的修改,如下:
private static void scenario2() {
James james = new James();
Lucy lucy = new Lucy();
AiFruitPlate<Orange> aiFruitPlate = james.getAiFruitPlate();
james.add(aiFruitPlate, new Apple());
lucy.eat(aiFruitPlate.get());
}
編譯器,會提示我們有錯誤,如圖:

通過以上案例,很清晰的知道我們?yōu)槭裁匆褂梅盒?,如下?/p>
- 消除類型轉(zhuǎn)換
- 在編譯時進行更強的類型檢查
- 增加代碼的復(fù)用性
泛型類(Generic Class)
泛型類是通過類型進行參數(shù)化的類,這樣說可能不是很好理解,之后我們用代碼演示。
普通類(A Simple Class)
首先,我們來定義一個普通的類,如下:
package definegeneric;
public class SimpleClass {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
它的 get 、set 方法接受和返回一個 Object,所以,我們可以隨意的傳遞任何類型。在編譯時無法檢查類型的使用,我們可以傳入 Integer 且取出 Integer,也可以傳入 String ,從而容易導(dǎo)致運行時錯誤。
泛型類(A Generic Class)
泛型類的定義格式如下:
class name<T1,T2,...,Tn>{
...
}
在類名之后的 <> 尖括號,稱之為類型參數(shù)(類型變量),定義一個泛型類就是使用 <> 給它定義類型參數(shù):T1、T2 ... Tn。
然后,我們把 SimpleClass 改成泛型類,如下:
package definegeneric;
public class GenericClass<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
所以的 object 都替換成為 T,類型參數(shù)可以定義為任何的非基本類型,如:class類型、interface類型、數(shù)組類型、甚至是另一個類型參數(shù)。
調(diào)用和實例化泛型類型(nvoking and Instantiating a Generic Type)
要想使用泛型類,必須執(zhí)行泛型類調(diào)用,如:
GenericClass<String> genericClass;
泛型類的調(diào)用類似于方法的調(diào)用(傳遞了一個參數(shù)),但是,我們沒有將參數(shù)傳遞給方法,而是,將類型參數(shù)(String)傳遞給了 GenericClass 類本身。
此代碼不會創(chuàng)建新的 GenericClass 對象,它只是聲明了 genericClass 將保存對 String 的引用
要實例化此類,要使用 new 關(guān)鍵字,如:
GenericClass<String> genericClass = new GenericClass<String>();
或者
GenericClass<String> genericClass = new GenericClass<>();
在 Java SE 7 或更高的版本中,編譯器可以從上下文推斷出類型參數(shù),因此,可以使用 <> 替換泛型類的構(gòu)造函數(shù)所需的類型參數(shù)
類型參數(shù)命名規(guī)范(Type Parameter Naming Conventions)
我們的類型參數(shù)是否一定要寫成 T 呢,按照規(guī)范,類型參數(shù)名稱是單個大寫字母。
常用的類型參數(shù)名稱有,如:
| 類型參數(shù) | 含義 |
|---|---|
| E | Element |
| K | Key |
| N | Number |
| V | Value |
| S,U,V... | 2nd, 3rd, 4th type |
多類型參數(shù)(Multiple Type Parameters)
泛型類可以有多個類型參數(shù),如:
public interface MultipleGeneric<K,V> {
public K getKey();
public V getValue();
}
public class ImplMultipleGeneric<K, V> implements MultipleGeneric<K, V> {
private K key;
private V value;
public ImplMultipleGeneric(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
public static void main(String[] args) {
MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<String, Integer>("per",6);
System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());
MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<String, String>("per","lsy");
System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());
}
}
輸出結(jié)果:
key:per, value:6
key:per, value:lsy
Process finished with exit code 0
如上代碼,new ImplMultipleGeneric 將 K 實例化為 String,將 V 實例化為 Integer ,因此, ImplMultipleGeneric 構(gòu)造函數(shù)參數(shù)類型分別為 String 和 Integer,在編寫 new ImplMultipleGeneric 代碼時,編輯器會自動填寫 <> 的值
由于,Java 編譯器會從聲明 ImplMultipleGeneric 推斷出 K 和 V 的類型,因此我們可以簡寫為,如下:
MultipleGeneric<String, Integer> m1 = new ImplMultipleGeneric<>("per",6);
System.out.println("key:" + m1.getKey() + ", value:" + m1.getValue());
MultipleGeneric<String,String> m2 = new ImplMultipleGeneric<>("per","lsy");
System.out.println("key:" + m2.getKey() + ", value:" + m2.getValue());
泛型接口(Generic Interface)
定義泛型接口和定義泛型類相似(泛型類的技術(shù)可同用于泛型接口),如下:
interface name<T1,T2,...,Tn>{
...
}
我們來定義一個泛型接口,如下:
package definegeneric;
public interface Genertor<T> {
public T next();
}
那么,如何實現(xiàn)一個泛型接口呢,我們使用兩種方式來實現(xiàn)泛型接口,如下:
使用泛型類,實現(xiàn)泛型接口,且不指定確切的類型參數(shù),所以,實現(xiàn)的 next() 返回值自動變成 T
package definegeneric.impl;
import definegeneric.Genertor;
public class ImplGenertor<T> implements Genertor<T> {
@Override
public T next() {
return null;
}
}
使用普通類,實現(xiàn)泛型接口,且指定確切的類型參數(shù)為 String,所以,實現(xiàn)的 next() 返回值自動變成 String
package definegeneric.impl;
import definegeneric.Genertor;
public class ImplGenertor2 implements Genertor<String> {
@Override
public String next() {
return null;
}
}
泛型方法(Generic Methods)
泛型方法使用了類型參數(shù)的方法,泛型方法比較獨立,可以聲明在 普通類、泛型類、普通接口、泛型接口中。
泛型方法定義格式,如下:
public <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2)
泛型方法的類型參數(shù)列表,在 <> 內(nèi),該列表必須在方法返回類型之前;對于靜態(tài)的泛型方法,類型參數(shù)必須在 static 之后,方法返回類型之前。
普通類里定義泛型方法(Generic methods in a Simple Class)
我們在普通類中定義泛型方法,如下:
package methodgeneric;
public class MethodGeneric {
//定義一個泛型方法
public <T> T genericMethod(T...t) {
return t[t.length/2];
}
public static void main(String[] args) {
MethodGeneric methodGeneric = new MethodGeneric();
System.out.println(methodGeneric.<String>genericMethod("java","dart","kotlin"));
}
}
methodGeneric.<String>genericMethod("java","dart","kotlin") 通??梢允÷缘?<> 的內(nèi)容,編譯器將推斷出所需的類型,和調(diào)用普通方法一樣,如:
methodGeneric.genericMethod("java","dart","kotlin")
泛型類里定義泛型方法(Generic methods in a Generic Class)
我們在泛型類中定義泛型方法,如下:
package methodgeneric;
public class MethodGeneric2 {
static class Fruit{
@Override
public String toString() {
return "fruit";
}
}
static class Apple extends Fruit {
@Override
public String toString() {
return "Apple";
}
}
static class Person{
@Override
public String toString() {
return "person";
}
}
//定義了泛型類
static class ShowClass<T> {
//定義了普通方法
public void show1(T t){
System.out.println(t.toString());
}
//定義了泛型方法
public <E> void show2(E e) {
System.out.println(e.toString());
}
//定義了泛型方法
public <T> void show3(T t) {
System.out.println(t.toString());
}
}
public static void main(String[] args) {
Apple apple = new Apple();
Person person = new Person();
ShowClass<Fruit> showClass = new ShowClass<>();
showClass.show1(apple); //可以放入 apple,因為 apple 是 fruit 的子類
showClass.show1(person); //此時,編譯器會報錯,因為 ShowClass<Fruit> 已經(jīng)限定類型
showClass.show2(apple); //可以放入,泛型方法 <E> 可以是任何非基本類型
showClass.show2(person);//可以放入,泛型方法 <E> 可以是任何非基本類型
showClass.show3(apple); //可以放入,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T,可以是任何非基本類型
showClass.show3(person); //可以放入,泛型方法 <T> 和泛型類中的 <T> 不是同一條 T,可以是任何非基本類型
}
}
在泛型類中定義泛型方法時,需要注意,泛型類里的泛型參數(shù) <T> 和泛型方法里的泛型參數(shù) <T> 不是同一個。
限定類型參數(shù)(Bounded Type Parameters)
我們經(jīng)常看到類似 public <U extends Number> void inspect(U u) 的代碼,<U extends Number> 就是限制類型參數(shù),只對數(shù)字進行操作且只接受 Number 或其子類。
要聲明一個限定的類型參數(shù),需要在參數(shù)類型后加上 extends 關(guān)鍵字,然后是其上限類型(類或接口)。
限定類型參數(shù)的泛型類(Generic Class of Bounded Type Parameters)
泛型類也可以使用限定類型參數(shù),如下:
package boundedgeneric;
public class BoundedClass<T extends Comparable> {
private T t;
public void setT(T t) {
this.t = t;
}
public T min(T outter){
if(this.t.compareTo(outter) > 0)
return outter;
else
return this.t;
}
public static void main(String[] args) {
BoundedClass<String> boundedClass = new BoundedClass<>(); //只能傳入實現(xiàn)了 Comparable 接口的類型
boundedClass.setT("iOS");
System.out.println(boundedClass.min("android"));
}
}
限定類型參數(shù)的泛型方法(Generic methods of Bounded Type Parameters)
泛型方法也可以使用限定類型參數(shù),如下:
package boundedgeneric;
public class BoundedGeneric {
public static <T extends Comparable> T min(T a, T b) {
if (a.compareTo(b) < 0)
return a;
else
return b;
}
public static void main(String[] args) {
System.out.println(BoundedGeneric.min(66,666));
}
}
多重限定(Multiple Bounds)
限定類型參數(shù),也可以為多個限定,如:
<T extends B1 & B2 & B3>
多個限定參數(shù),如果其中有類,類必須放在第一個位置,例如:
interface A { ... }
interface B { ... }
class C { ... }
class D <T extends C & A & B>
泛型,繼承和子類型(Generics, Inheritance, and Subtypes)
在前面的盤子裝水果小故事里我們已經(jīng)創(chuàng)建好了一些水果類,如下:
public class Fruit {
@Override
public String toString() {
return "This is Fruit";
}
}
public class Apple extends Fruit {
@Override
public String toString() {
return " Apple ??";
}
}
public class Orange extends Fruit {
@Override
public String toString() {
return " Orange ??";
}
}
public class QIOrange extends Orange {
@Override
public String toString() {
return "qi Orange ??";
}
}
他們的繼承關(guān)系,如圖:

眾所周知,我們可以把子類賦值給父類,例如:
Apple apple = new Apple();
Fruit fruit = new Fruit();
fruit = apple;
泛型也是如此,我們定義一個水果盤子的泛型類,如下:
public class FruitPlateGen<Fruit> implements Plate<Fruit> {
private List<Fruit> fruits = new ArrayList<>(6);
@Override
public void set(Fruit fruit) {
fruits.add(fruit);
}
@Override
public Fruit get() {
int index = fruits.size() - 1;
if(index >= 0) return fruits.get(index);
return null;
}
}
所以,是 Fruit 的子類都可以放入水果盤里,如下:
FruitPlateGen<Fruit> fruitPlate = new FruitPlateGen<Fruit>();
fruitPlate.set(new Apple());
fruitPlate.set(new Orange());
現(xiàn)在,James 可以獲取盤子,如下:
public class James extends Person {
public FruitPlateGen getAiFruitPlateGen(FruitPlateGen<Fruit> plate) {
return new FruitPlateGen();
}
}
如是,James 想獲取放橘子的盤子,如下:
James james = new James();
james.getAiFruitPlateGen(new FruitPlateGen<Fruit>()); //獲取成功
james.getAiFruitPlateGen(new FruitPlateGen<Orange>()); //編譯器報錯
雖然,Orange 是 Fruit 的子類,但是,FruitPlateGen<Orange> 不是 FruitPlateGen<Fruit> 的子類,所以,不能傳遞產(chǎn)生繼承關(guān)系。

泛型類和子類型(Generic Classes and Subtyping)
我們可以通過繼承(extends)或?qū)崿F(xiàn)(implements)泛型類或接口,例如:
private static class ExtendFruitPlate<Orange> extends FruitPlateGen<Fruit> {
}
此時,ExtendFruitPlate<Orange> 就是 FruitPlateGen<Fruit> 的子類,James 再去拿盤子,就不會有錯誤提示:
james.getAiFruitPlateGen(new ExtendFruitPlate<Orange>());
通配符(Wildcards)
我們經(jīng)??吹筋愃?List<? extends Number> 的代碼,? 就是通配符,表示未知類型。
上限通配符(Upper Bounded Wildcards)
我們可以使用上限通配符來放寬對變量的限制,例如,上文提到的 FruitPlateGen<Fruit> 和 FruitPlateGen<Orange>() 就可以使用上限通配符。
我們來改寫一下 getAiFruitPlateGen 方法,如下:
public FruitPlateGen getAiFruitPlateGen2(FruitPlateGen<? extends Fruit> plate) {
return new FruitPlateGen();
}
這時候,James 想獲取放橘子的盤子,如下:
James james = new James();
james.getAiFruitPlateGen2(new FruitPlateGen<Fruit>()); //獲取成功
james.getAiFruitPlateGen2(new FruitPlateGen<Orange>()); //獲取成功
上限通配符 FruitPlateGen<? extends Fruit> 匹配 Fruit 和 Fruit 的任何子類型,所以,我們可以傳入 Apple、Orange 都沒有問題。
下限通配符(Lower Bounded Wildcards)
上限通配符將未知類型限定為該類型或其子類型,使用 extends 關(guān)鍵字,而下限通配符將未知類型限定為該類型或其父類型,使用 super 關(guān)鍵字。
我們再來寬展一下 getAiFruitPlateGen 方法,如下:
public FruitPlateGen getAiFruitPlateGen3(FruitPlateGen<? super Apple> plate) {
return new FruitPlateGen();
}
這時候,James 只能獲取 FruitPlateGen<Fruit> 和 FruitPlateGen<Apple> 的盤子,如下:
James james = new James();
james.getAiFruitPlateGen3(new FruitPlateGen<Apple>());
james.getAiFruitPlateGen3(new FruitPlateGen<Fruit>());
下限通配符 FruitPlateGen<? super Apple> 匹配 Apple 和 Apple 的任何父類型,所以,我們可以傳入 Apple、Fruit。
通配符和子類型(Wildcards and Subtyping)
在 泛型,繼承和子類型 章節(jié)有講到,雖然,Orange 是 Fruit 的子類,但是,FruitPlateGen<Orange> 不是 FruitPlateGen<Fruit> 的子類。但是,你可以使用通配符在泛型類或接口之間創(chuàng)建關(guān)系。
我們再來回顧下 Fruit 的繼承關(guān)系,如圖:

代碼,如下:
Apple apple = new Apple();
Fruit fruit = apple;
這個代碼是沒有問題的,Fruit 是 Apple 的父類,所以,可以把子類賦值給父類。
代碼如下:
List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = apples; // 編輯器報錯
因為,List<Apple> 不是 List<Fruit> 的子類,實際上這兩者無關(guān),那么,它們的關(guān)系是什么?如圖:

List<Apple> 和 List<Fruit> 的公共父級是 List<?>。
我們可以使用上下限通配符,在這些類之間創(chuàng)建關(guān)系,如下:
List<Apple> apples = new ArrayList<>();
List<? extends Fruit> fruits1 = apples; // OK
List<? super Apple> fruits2 = apples; // OK
下圖展示了上下限通配符聲明的幾個類的關(guān)系,如圖:

PECS原則(Producer extends Consumer super)
在上文中有 FruitPlateGen 水果盤子的類,我們嘗試使用上下限通配符來實例化水果盤,代碼如下:
Apple apple = new Apple();
Orange orange = new Orange();
Fruit fruit = new Fruit();
FruitPlateGen<? extends Fruit> fruitPlateGen = new FruitPlateGen<>();
fruitPlateGen.set(apple); // error
fruitPlateGen.set(orange); // error
fruitPlateGen.set(fruit); // error
Fruit fruit1 = fruitPlateGen.get(); // OK
Orange orange1 = fruitPlateGen.get(); // error
Apple apple1 = fruitPlateGen.get(); // error
上限通配符無法 set 數(shù)據(jù),但是,可以 get 數(shù)據(jù)且只能 get 到其上限 Fruit,所以,上限通配符可以安全的訪問數(shù)據(jù)。
在來看一下代碼,如下:
FruitPlateGen<? super Apple> fruitPlateGen1 = new FruitPlateGen<>();
fruitPlateGen1.set(apple); // OK
fruitPlateGen1.set(orange); // error
fruitPlateGen1.set(fruit); // error
Object object = fruitPlateGen1.get(); // OK
Fruit fruit2 = fruitPlateGen1.get(); // error
Apple apple2 = fruitPlateGen1.get(); // error
Orange orange2 = fruitPlateGen1.get(); // error
下限通配符可以且只能 set 其下限 Apple,也可以 get 數(shù)據(jù),但只能用 Object 接收(因為Object是所有類型的父類,這是一個特例),所以,下限通配符可以安全的寫入數(shù)據(jù)。
所以,在使用上下限通配符時,可以遵循以下準(zhǔn)則:
- 如果你只需要從集合中獲得類型T , 使用<? extends T>通配符
- 如果你只需要將類型T放到集合中, 使用<? super T>通配符
- 如果你既要獲取又要放置元素,則不使用任何通配符
類型擦除(Type Erasure)
Java 語言使用類型擦除機制實現(xiàn)了泛型,類型擦除機制,如下:
- 編譯器會把所有的類型參數(shù)替換為其邊界(上下限)或 Object,因此,編譯出的字節(jié)碼中只包含普通類、接口和方法。
- 在必要時插入類型轉(zhuǎn)換,已保持類型安全
- 生成橋接方法以在擴展泛型類時保持多態(tài)性
泛型類型的擦除(Erasure of Generic Types)
Java 編譯器在擦除過程中,會擦除所有類型參數(shù),如果類型參數(shù)是有界的,則替換為第一個邊界,如果是無界的,則替換為 Object。
我們定義了一個泛型類,代碼如下:
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) { this.data = data;
this.next = next;
}
public T getData() { return data; }
...
}
由于類型參數(shù) T 是無界的,因此,Java 編譯器將其替換為 Object,如下:
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) { this.data = data;
this.next = next;
}
public Object getData() { return data; }
...
}
我們再來定義一個有界的泛型類,代碼如下:
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) { this.data = data;
this.next = next;
}
public T getData() { return data; }
...
}
Java 編譯器其替換為第一個邊界 Comparable,如下:
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) { this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
...
}
泛型方法的擦除(Erasure of Generic Methods)
Java 編譯器同樣會擦除泛型方法中的類型參數(shù),例如:
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
}
由于 T 是無界的,因此,Java 編譯器將其替換為 Object,如下:
public static int count(Object[] anArray, Object elem) {
int cnt = 0;
for (Object e : anArray) if (e.equals(elem))
}
如下代碼:
class Shape { ... }
class Circle extends Shape { ... }
class Rectangle extends Shape { ... }
有一個泛型方法,如下:
public static<T extends Shape> void draw(T shape){
...
}
Java 編譯器將用第一個邊界 Shape 替換 T,如下:
public static void draw(Shape shape){
...
}
橋接方法(Bridge Methods)
有時類型擦除會導(dǎo)致無法預(yù)料的情況,如下:
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
類型擦除后,代碼如下:
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
此時,Node 的方法變?yōu)?setData(Object data) 和 MyNode 的 setData(Integer data) 不會覆蓋。
為了解決此問題并保留泛型類型的多態(tài)性,Java 編譯器會生成一個橋接方法,如下:
class MyNode extends Node {
// 生成的橋接方法
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
...
}
這樣 Node 的方法 setData(Object data) 和 MyNode 生成的橋接方法 setData(Object data) 可以完成方法的覆蓋。
泛型的限制(Restrictions on Generics)
為了有效的使用泛型,需要考慮以下限制:
- 無法實例化具有基本類型的泛型類型
- 無法創(chuàng)建類型參數(shù)的實例
- 無法聲明類型為類型參數(shù)的靜態(tài)字段
- 無法將Casts或instanceof與參數(shù)化類型一起使用
- 無法創(chuàng)建參數(shù)化類型的數(shù)組
- 無法創(chuàng)建,捕獲或拋出參數(shù)化類型的對象
- 無法重載每個重載的形式參數(shù)類型都擦除為相同原始類型的方法
無法實例化具有基本類型的泛型類型
代碼如下:
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
...
}
創(chuàng)建對象時,不能使用基本類型替換參數(shù)類型:
Pair<int, char> p = new Pair<>(8, 'a'); // error
無法創(chuàng)建類型參數(shù)的實例
代碼如下:
public static <E> void append(List<E> list) {
E elem = new E(); // error
list.add(elem);
}
無法聲明類型為類型參數(shù)的靜態(tài)字段
代碼如下:
public class MobileDevice<T> {
private static T os; // error
...
}
類的靜態(tài)字段是所有非靜態(tài)對象共享的變量,因此,不允許使用類型參數(shù)的靜態(tài)字段。
無法將Casts或instanceof與參數(shù)化類型一起使用
代碼如下:
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // error
...
}
}
Java 編譯器會擦除所有類型參數(shù),所有,無法驗證在運行時使用的參數(shù)化類型。
無法創(chuàng)建參數(shù)化類型的數(shù)組
代碼如下:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // error
無法創(chuàng)建,捕獲或拋出參數(shù)化類型的對象
代碼如下:
class MathException<T> extends Exception { ... } // error
class QueueFullException<T> extends Throwable{ ... } // error
無法重載每個重載的形式參數(shù)類型都 擦除為相同原始類型的方法
代碼如下:
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
print(Set<String> strSet) 和 print(Set<Integer> intSet) 在類型擦除后是完全相同的類型,所以,無法重載。