泛型的基本使用可以参考我的这篇文章(教你如何使用泛型),然而,当你真正使用泛型时,还需特别小心一些陷阱。本篇文章主要为你介绍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语言。