一、引
最近发现一个问题,LinkedList<String> ls 不可以被赋值给LinkedList<Object> lo,这是为什么呢?
这是因为Java中是泛型是伪泛型。虽然上面两个泛型的类具有继承关系,但是这两个List之间本身是没有关系的。当上述的
LinkdedList被编译之后,class文件中其变成了LinkedList list,存在泛型擦除的过程。这样来看,上面两个LinkedList其实是可以包
含任何对象的。这个时候如果在泛型为Object的List里插入一个Integer,是没有任何问题的,但是通过引用的赋值方式,所以在
String泛型的List中也有了一个Integer对象,这是非常不合理的,也违背了泛型的意义。
二、泛型擦除
上面提到了泛型擦除这个概念,那么下面就从这点展开,分析一下:
import java.util.*;
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}/* Output:
true
*///:~
上述代码中,结果为true。虽然我们定义了两个泛型不同的List,但当我们声明List<String>和List<Integer>时,在运行时实际上
是相同的,都是List,而具体的类型参数信息String和Integer被擦除了。这就导致一个很麻烦的问题:在泛型代码内部,无法获得任
何有关泛型参数类型的信息。
再举一个例子:
public class HasF {
public void f() { System.out.println("HasF.f()"); }
}
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
// Error: cannot find symbol: method f():
public void manipulate() { obj.f(); }
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator =
new Manipulator<HasF>(hf);
manipulator.manipulate();
}
}
上面的例子中,在Manipulator类的manipulate方式报错,找不到这个方法。在Java代码运行的时候,它会将泛型类的类型信息
T擦除掉,就是说运行阶段,泛型类代码内部完全不知道类型参数的任何信息。如上面代码,运行阶段Manipulator<HasF>类的类型
信息会被擦除,只剩下Mainipulator,所以我们在Manipulator内部并不知道传入的参数类型时HasF的,所以代码中obj调用f自然就
会报错。
同理,在泛型类或者泛型方法内部,不能够把泛型显式的引用运行时类型的操作之中。比如转型、new操作、instance判断、调
用泛型类的方法中。
三、泛型擦除的来由
既然泛型擦除有这么多不方便的地方,那么为什么还要加入这个性质呢?
这是一个历史问题,Java在版本1.0中是不支持泛型的,这就导致了很大一批原有类库是在不支持泛型的Java版本上创建的。
而到后来Java逐渐加入了泛型,为了使得原有的非泛化类库能够在泛化的客户端使用,Java开发者使用了擦除进行了折中。所以
Java使用这么具有局限性的泛型实现方法就是从非泛化代码到泛化代码的一个过渡,以及不破坏原有类库的情况下,将泛型融入
Java语言。
四、如何避免泛型擦除带来的问题
第一种方法就是换语言(说了和没说一样。。),python和c++中不存在泛型擦除。
第二种就是利用Java的RTTI即运行时类型信息来实现泛型类中new的功能:
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
T t;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public void newT(){
t = kind.newInstance();
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 =
new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 =
new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}/* Output:
true
false
true
*///:~
第三种就是利用受限泛型,采用extends和super设置泛型的上下界。
LinkedList<? extends Building>,LInkedList<? super House>分别设置了上界和下界。在这种情况下编译器不会将泛型信息完
全擦除,比如上述两个List经过编译后分别编程了LinkedList<Building> 和 LinkedList<House>。同理在调用泛化类其中的泛化方法的
时候可以采用Manipulator<T extends 接口名>来告诉编译器有哪些方法可以调用。