泛型的基本使用可以參考我的這篇文章(教你如何使用泛型),然而,當你真正使用泛型時,還需特別小心一些陷阱。本篇文章主要爲你介紹Java的泛型的類型檫除,以及類型檫除會帶來哪些問題,如何正確的處理這些問題。
首先,我們先看一個例子。
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass());
System.out.println(intList.getClass());
}
我們大部分人都會認爲ArrayList<String>
與ArrayList<Integer>
是不同的類型。然而,當我們運行上述的代碼時,返回的結果都是class java.util.ArrayList
。爲什麼會出現這樣的結果呢??
這是因爲類型檫除的效果。
類型檫除(type erasure) : generic type information is present only at compile time, after which it is erased by the compiler.
簡單的說,就是當你在使用泛型時,任何具體的信息都被檫除了。在泛型代碼的內部,無法獲得任何有關泛型參數類型的信息。因此,在上述代碼中,ArrayList<String>
和ArrayList<integer>
是相同的類型。
Java設計很大程度上是受C++的啓發。而Java 泛型是Java SE5時才提出來的。如果你以前學過C++,那麼你會發現,有些以前C++可以做到的,而Java卻不能做到。爲了加深對泛型的理解,我們再看一個例子。
當我們使用C++模板時,我們可以這樣寫。
#include <iostream>
using namespace std;
template<class T> class Manipulator{
T obj;
public:
Manipulator(T x){
obj = x;
}
void manipulate(){
//注意這個方法。
obj.f();
}
};
class HasF{
public:
void f(){
cout<<"call to f()";
}
};
int main()
{
HasF hasF;
Manipulator<HasF> man(hasF);
man.manipulate();
// cout << "Hello world!" << endl;
return 0;
}
看到上面的代碼,我們可能會奇怪,obj.f()
.obj
怎麼會知道f()
是爲類型參數T
而存在的呢?因爲,當實例化模板類時,C++編譯器將進行檢查,在Manipulator<HasF>
實例化時,它會知道HasF
擁有一個f()
。
用C++寫這種代碼是很容易的,因爲當模板被實例化時,模板代碼知道模板參數的類型。
那麼,Java泛型該如何實現呢??
如果直接像C++上面直接調用HasF
中的方法f()
,編譯器會報錯。因爲類型檫除,所以無法獲得泛型參數類型的信息。爲了調用f()
方法,我們必須協助泛型類,給定泛型類的邊界,告知編譯器只能接受遵循這個邊界的類型。
代碼如下。
HasF.java
package genericdemo.typeerasure;
public class HasF {
void f(){
System.out.println("call to f()");
}
}
Manipulator.java
package genericdemo.typeerasure;
import java.util.ArrayList;
public class Manipulator<T extends HasF> {
private T obj;
public Manipulator(T x) {
obj = x;
}
public void method(){
obj.f();
}
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<String>();
ArrayList<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass());
System.out.println(intList.getClass());
System.out.println(strList.getClass() == intList.getClass());
HasF hasF = new HasF();
Manipulator<HasF> man = new Manipulator<HasF>(hasF);
man.method();
}
}
可能你會問,既然類型擦除會帶來種種問題,那麼,爲什麼還要使用擦除呢??首先,你要明白,Java一開始是不支持泛型的。泛型是Java SE5所作出的重大改變。如果從Java 1.0時,就支持泛型,那麼設計者肯定不會使用擦除。因此,之所以使用擦除,是在不破壞現有類庫的情況下,將泛型融入Java語言。