我在自己总结的【Java心得总结三】Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码:
复制代码
1 import java.util.*;
2 public class ErasedTypeEquivalence {
3 public static void main(String[] args) {
4 Class c1 = new ArrayList().getClass();
5 Class c2 = new ArrayList().getClass();
6 System.out.println(c1 == c2);
7 }
8 }/* Output:
9 true
10 *///:~
复制代码
在代码的第4行和第5行,我们分别定义了一个接受String类型的List和一个接受Integer类型的List,按照我们正常的理解,泛型ArrayList虽然是相同的,但是我们给它传了不同的类型参数,那么c1和2的类型应该是不同的。但是结果恰恰想法,运行程序发现二者的类型时相同的。这是为什么呢?这里就要说到Java语言实现泛型所独有的——擦除(万恶啊)
即当我们声明List和List时,在运行时实际上是相同的,都是List,而具体的类型参数信息String和Integer被擦除了。这就导致一个很麻烦的问题:在泛型代码内部,无法获得任何有关泛型参数类型的信息 (摘自《Java编程思想第4版》)。
为了体验万恶的擦除的“万恶”,我们与C++做一个比较:
C++模板:
复制代码
1 #include
2 using namespace std;
3 template class Manipulator {
4 T obj;
5 public:
6 Manipulator(T x) { obj = x; }
7 void manipulate() { obj.f(); }
8 };
9 class HasF {
10 public:
11 void f() { cout << "HasF::f()" << endl; }
12 };
13 int main() {
14 HasF hf;
15 Manipulator manipulator(hf);
16 manipulator.manipulate();
17 } /* Output:
18 HasF::f()
19 ///:~
复制代码
在这段代码中,我们声明了一个模板(即泛型)类Manipulator,这个类接收一个T类型的对象,并在内部调用该对象的f方法,在main我们向Manipulator传入一个拥有f方法的类HasF,然后代码很正常的通过编译而且顺利运行。
C++代码里其实有一个很奇怪的地方,就是在代码第7行,我们利用传入的T类型对象来调用它的f方法,那么我怎么知道你传入的类型参数T类型是否有方法f呢?但是从整个编译来看,C++中确实实现了,并且保证了整个代码的正确性(可以验证一个没有方法f的类传入,就会报错)。至于怎么做到,我们稍后会略微提及。
OK,我们将这段代码用Java实现下:
Java泛型:
复制代码
1 public class HasF {
2 public void f() { System.out.println("HasF.f()"); }
3 }
4 class Manipulator {
5 private T obj;
6 public Manipulator(T x) { obj = x; }
7 // Error: cannot find symbol: method f():
8 public void manipulate() { obj.f(); }
9 }
10 public class Manipulation {
11 public static void main(String[] args) {
12 HasF hf = new HasF();
13 Manipulator manipulator =
14 new Manipulator(hf);
15 manipulator.manipulate();
16 }
17 } ///:~
复制代码
大家会发现在C++我们很方便就能实现的效果,在Java里无法办到,在代码第7行给出了错误提示,就是说在Manipulator内部我们无法获知类型T是否含有方法f。这是为什么呢?就是因为万恶的擦除引起的,在Java代码运行的时候,它会将泛型类的类型信息T擦除掉,就是说运行阶段,泛型类代码内部完全不知道类型参数的任何信息。如上面代码,运行阶段Manipulator类的类型信息会被擦除,只剩下Mainipulator,所以我们在Manipulator内部并不知道传入的参数类型时HasF的,所以第8行代码obj调用f自然就会报错(就是我哪知道你有没有f方法啊)
综上,我们可以看出擦除带来的代价:在泛型类或者说泛型方法内部,我们无法获得任何类型信息,所以泛型不能用于显示的引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。例如下代码:
复制代码
1 public class Animal{
2 T a;
3 public Animal(T a){
4 this.a = a;
5 }
6 // error!
7 public void animalMove(){
8 a.move();
9 }
10 // error!
11 public void animalBark(){
12 a.bark();
13 }
14 // error!
15 public void animalNew(){
16 return new T();
17 }
18 // error!
19 public boolean isDog(){
20 return T instanceof Dog;
21 }
22 }
23 public class Dog{
24 public void move(){
25 System.out.println("dog move");
26 }
27 public void bark(){
28 System.out.println("wang!wang!);
29 }
30 }
31 public static void main(String[] args){
32 Animal ad = new Animal();
33 }
复制代码
我们声明一个泛化的Animal类,之后声明一个Dog类,Dog类可以移动move(),吠叫bark(