前言
泛型是現(xiàn)代編程語言很重要的組成部分,它的出現(xiàn)有兩個目的:
- 提供更嚴密的編譯器類型檢查
- 支持泛型編程
Java從JDK5開始添加了對泛型支持,本文將詳述Java(JDK8)中泛型的語法,原理,高級用法和局限.
正文
泛型類和方法的定義
//非嚴謹定義,僅表示基本結構
/**
*泛型類
*/
public class class_name<T1, T2, ..., Tn> { /* ... */ }
/**
*泛型方法
*/
public <T1,T2,...,Tn> void method_name(T1 t1,T2 t2,...Tn){
/* ... */ }
//eg.
//創(chuàng)建單Parameter的泛型類
class A<T>{ /* ... */ }
//創(chuàng)建多Parameter泛型類
class B<T,V>{ /* ... */ }
?
class A<T> {
//使用類Type Parameter的方法
public void method1(T t) {
/* ... */
}
//泛型方法
public <R> void method2(R r){
/* ... */
}
//混合類Type Parameter和方法Type Parameter的方法
public <R> void method3(R r, T t) {
/* ... */
}
}
?
T1,T2,...Tn被稱為 Type Parameter(類型參數(shù),詳見Type Parameter 和 Type Argument )
泛型類和方法的使用
Parameterized Types和Raw Types
如果生成泛型類對象時指定了具體的Type Argument,就稱之為Parameterized Types,例如下面這句就生成了Parameterized Types為Integer的ArrayList.
new ArrayList<Integer>();
如果生成時沒有指定Type Argument,就稱為 Raw Types,例如下標這句生成了Raw Types的ArrayLis
//編譯時會提示 Unchecked assignment: 'java.util.ArrayList' to 'java.util.List<java.lang.Integer>'
List<Integer> list=new ArrayList();
為什么會有Raw Types呢? 假設現(xiàn)在有一個JDK5之前編寫的方法,在Raw Types的支持下,老代碼無需修改就能獲得泛型的便利,我們可以直接這樣寫,一方面減輕了用戶更新JDK版本的難度,另一方面保證了JDK的兼容性.
public List getList(){/*...*/}
List<String> result=getList()
基本用法
/**
*創(chuàng)建泛型類對象
*/
new class_name()<>();
/**
*手動指定Type Argument的泛型方法
*/
obj.<Type argument>method_name(args)
?
class A<T> {
//使用類Type Parameter的方法
public void method1(T t) {}
//泛型方法
public <R> void method2(R r){}
//混合類Type Parameter和方法Type Parameter的方法
public <R> void method3(R r, T t) {}
}
//創(chuàng)建泛型類對象
A<String> a = new A<>();
//1 Parameterized Types在泛型類對象創(chuàng)建的時候已經(jīng)確定了,A的 Parameterized Types是String
a.method1("abc");
//2 Parameterized Types會根據(jù)傳入argument的類型自動推導為String
a.method2("abc");
//3 顯式指定了Parameterized Types,不再使用自動推導.簽名為method1(Long t)
a.<Long> method2(0L);
//4 來自泛型類的Type Parameter由泛型類確定,方法自身的泛型參數(shù)由傳入的argument決定
a.method3(0, "abc");
Java中的泛型實現(xiàn)原理
Java中的泛型使用類型擦除方式實現(xiàn),JVM并不知道泛型,所有的泛型在編譯階段就已經(jīng)被處理成了普通類和方法,并體現(xiàn)在生成字節(jié)碼上
擦除原理代碼演示
public static void eraseTest() {
List l1 = new ArrayList();
l1.add("abc");
l1.add(Integer.valueOf(123));
String s1 = (String) l1.get(0);
Integer i1 = (Integer) l1.get(1);
List<String> l2 = new ArrayList();
l2.add("abc");
// IDE報錯提示: 類型不符
// l2.add(Integer.valueOf(123));
String s2 = l2.get(0);
List<String> l3 = new ArrayList<>();
l3.add("abc");
// IDE報錯提示: 類型不符
// l3.add(Integer.valueOf(123));
String s3 = (String) l1.get(0);
}
//對應的字節(jié)碼
public static void eraseTest();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=7, args_size=0
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_0
8: aload_0
9: ldc #4 // String abc
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_0
18: bipush 123
20: invokestatic #6 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
23: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
28: pop
29: aload_0
30: iconst_0
31: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
36: checkcast #8 // class java/lang/String
39: astore_1
40: aload_0
41: iconst_1
42: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
47: checkcast #9 // class java/lang/Integer
50: astore_2
51: new #2 // class java/util/ArrayList
54: dup
55: invokespecial #3 // Method java/util/ArrayList."<init>":()V
58: astore_3
59: aload_3
60: ldc #4 // String abc
62: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
67: pop
68: aload_3
69: iconst_0
70: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
75: checkcast #8 // class java/lang/String
78: astore 4
80: new #2 // class java/util/ArrayList
83: dup
84: invokespecial #3 // Method java/util/ArrayList."<init>":()V
87: astore 5
89: aload 5
91: ldc #4 // String abc
93: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
98: pop
99: aload_0
100: iconst_0
101: invokeinterface #7, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
106: checkcast #8 // class java/lang/String
109: astore 6
111: return
使用綁定的類型(或Object)替換泛型中的Type Parameter,這樣生成的字節(jié)碼中,只包含原始的類,接口和方法
首先看 0~4,51~55,80~84 三種創(chuàng)建泛型類對象的方式生成的字節(jié)碼是相同的,都是
// Method java/util/ArrayList."<init>":()V
再看11,62,93 ,add方法的簽名也都相同,參數(shù)都是Object而非具體的Type Argument
InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
為了保證類型安全,在必要的地方加入類型轉換
而36,75,106中 36對應的字節(jié)碼checkcast 是由源代碼編譯出的,而75,106都是編譯器對泛型處理后自動生成的
在有繼承的泛型類中使用橋接方法(bridge methods)保證多態(tài)
class A {
public <T> void method(T t) {
}
}
class B extends A {
@Override
public <T> void method(T t) {
}
}
//類A對應字節(jié)碼
public void method(T);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 15: 0
Signature: #14 // (TT;)V
//類B對應字節(jié)碼
public void method(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 23: 0
//橋接方法
public void method(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #3 // class java/lang/String
5: invokevirtual #4 // Method method:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 19: 0
泛型擦除后,A中method簽名為 method(Object) 而B繼承Parameterized Types為String的A后,重寫的method簽名為method(String),這就導致多態(tài)出現(xiàn)問題, 為了解決這個問題,編譯器會自動生成一個橋接方法, 簽名為 method(Object),這個方法內(nèi)部去調(diào)用B重寫的method. 保證多態(tài).
高級用法
定義時限定Type Parameter上界
限定上界,extends后如果跟隨多個,第一個必須為類或抽象類,后面的必須為接口,使用時Type Argument 必須同時滿足所有限定條件
/**
*限定上界的類定義
*/
class class_name <T extends class_or_ abstract_class_name&interface_name&interface_name>
/**
* 限定上界的方法定義
*/
public <T extends class_or_ abstract_class_name&interface_name&interface_name> void method2(T t)
//eg.
class C<T extends Number> extends ArrayList<T> {
}
public <T extends Number & Serializable> void method2(T t) {
}
沒有限定下屆的語法,個人認為是意義不大,所以沒有.
通配符?在泛型中的應用
PECS(Producer Extends Consumer Super)規(guī)則
- 如果一個對象頻繁向外讀取數(shù)據(jù)的,就被稱為 "in" ,適合用上界 extends
- 如果一個對象經(jīng)常向里插入數(shù)據(jù),就被稱為"out"適合用下界super
場景設定: Plate可以裝載物品, Food,Fruit,Apple,Banana有繼承關系.
static class Plate<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
static class Food {
}
static class Fruit extends Food {
}
static class Apple extends Fruit {
}
static class Banana extends Fruit {
}
限定上界

Plate<Apple> applePlate = new Plate<>();
applePlate.setT(new Apple());
Plate<? extends Fruit> p = applePlate;
// 編譯錯誤,類型不符
// p.setT(new Apple());
Fruit f = p.getT();
f.printName();
不能存,只能取, 且取出的是上界對應的對象. <? extends Fruit>只知道里面存的是Fruit或Fruit的子類,標記為#A,具體是什么不知道,想插入數(shù)據(jù)時不知道和#A是否匹配,所以存不進去,取出時同理,只能當做Fruit取出.
限定下界

Plate<? super Fruit> plate = new Plate<>();
plate.setT(new Fruit());
Object obj = plate.getT();
((Fruit) obj).printName();
能存,存的必須是Fruit或Fruit的直接/間接父類, 可以取,因為Object是所有類的基類,因此只能當做Object取
無界
Plate<Apple> applePlate = new Plate<>();
applePlate.setT(new Apple());
Plate<?> plate = applePlate;
Object obj=plate.getT();
((Apple) obj).printName();
不能存,只能取,且取出時只能為Object.它有兩個用途
- 泛型類僅用到Object中定義的方法, 例如 Object.hashcode().
- 泛型類不依賴Type Parameter的類型.
<?>和<Object>是有差別的,<?>只能取,不能存, 而<Object>是可以作為Object存的.
為什么用了通配符?某些場景下不能存,某些可以
上面講過類型擦除為了保證類型安全,在必要的地方加入類型轉換,這就要有有一個確定的類型,<? extends X> 可以確保是X或X的子類,由于類的繼承原理: X的子類可以強轉為X,添加強轉是使用X即可.因此可以存,而<? super X>和<?> 都沒有確定的類型,都表示一個范圍,因此不能存.
泛型的繼承關系
泛型不會繼承普通類的繼承關系

要完成泛型中的繼承,需要使用 ?, ?在繼承中對泛型的作用類似于Object.

擦除的弊端
上面說過,擦除法是在編譯進階擦除類型信息,這也就導致運行時獲取不到類型信息.
- reifiable type: 運行時全部信息都可以獲取到的types,包含primitive,no-generic,raw(未指定實際類型的泛型),無界類型(如List<?>)
- no-reifiable type: 信息已在編譯時因類型擦除被移除的類型,例如List<String>和List<Number>,JVM無從得知兩者運行時的差別,很多場景下 non-reifiable是無法使用的
泛型弊端演示
- 無法編譯的
<?>
下面這段代碼是無法編譯通過的,原因上面的完全wildcard講過
void foo(List<?> i) {
i.set(0, i.get(0));
}
要解決這個問題,可以添加一個輔助方法
void foo(List<?> i) {
forHelp(i);
}
<T> void forHelp(List<T> i) {
i.set(0, i.get(0));
}
- WildcardErrorBad
void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
// got a CAP#2 extends Number;
// same bound, but different types
l2.set(0, temp); // expected a CAP#1 extends Number,
// got a Number
}
這邊這段代碼從語法上就是錯誤的,但是報錯信息可能會讓人迷惑,現(xiàn)在假設有這些數(shù)據(jù)
List<Integer> l1 = Arrays.asList(1, 2, 3);
List<Double> l2 = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(l1, l2);
很明顯, l2中的數(shù)據(jù)是Double類型,而l1中的數(shù)據(jù)時Integer類型,當然無法set.
- Cannot Instantiate Generic Types with Primitive Types原始類型無法作為泛型實例化Type
//compile error!
List<int,int> il
- Cannot Create Instances of Type Parameters無法創(chuàng)建Type Parameters的實例
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
一種可行的解決方案
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
- Cannot Declare Static Fields Whose Types are Type Parameters不能聲明泛型靜態(tài)域
//compile error
public class MobileDevice<T> {
private static T os;
// ...
}
- Cannot Use Casts or instanceof With Parameterized Types不能對Parameterized Type使用cast或instanceof
//ArrayList<Integer>, ArrayList<String> ,LinkedList<Character>都可能被傳遞進去,而runtime并不會追蹤Type Parameters
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // compile-time error
// ...
}
}
// compile-time error
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li;
//OK
List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;
- Cannot Create Arrays of Parameterized Types不能創(chuàng)建Parameterized Type的數(shù)組,編譯期被禁止,因為運行時無法分辨
Object[] stringLists = new List<String>[2]; // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,
// but the runtime can't detect it.
Cannot Create, Catch, or Throw Objects of Parameterized Types不能創(chuàng)建,捕捉或拋出Parameterized Type
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
- Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type不能重載擦除后raw type相同的方法
// compile error
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
后記
寫這篇文章的起因是在做一個需求的時候,同事用泛型很好的封裝了一個基礎組件,優(yōu)雅簡潔,我卻完全沒有想到,還在傻傻的強轉類型轉換. 很早之前我就研究過泛型并且做了筆記, 趁這次機會回顧實踐一下并寫了這篇文章.