Java泛型中的擦除

来源:互联网 发布:怎样申请淘宝小二介入 编辑:程序博客网 时间:2024/05/23 15:47

1、擦除

当深入了解泛型后,就会发现很多东西初看起来是没有意义的。例如,尽管可以申明ArrayList.class,但是不能申明为Array<Integer>.class。例如:

class Manipulator<T> {  private T obj;  public Manipulator(T x) { obj = x; }  // Error: cannot find symbol: method f()://  public void manipulate() { obj.f(); }}

经过编译后生成的.class文件如下:

class Manipulator{    public Manipulator(Object x)    {        obj = x;    }    private Object obj;}

  即在class Manipulator<T>内部,T都被Object类型替代,这就是擦除,即使你Manipulator<String> manipulator = new Manipulator<String>("a")调用时注入的不是Object类型,它依然会被擦除成Object类型,所以不能在class Manipulator<T>内部使用调用T的类型所特有的方法,只能使用Object能使用的那些方法。当然你可以协助泛型类,给定泛型的边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了extends关键字。例如:

class Manipulator2<T extends HasF> {  private T obj;  public Manipulator2(T x) {  System.out.println();obj = x; }  public void manipulate() { obj.f(); }}

反编译后得到:

class Manipulator2{    public Manipulator2(HasF x){        System.out.println();        obj = x;    }    public void manipulate(){        obj.f();    }    private HasF obj;}

  <T extends HasF>表明T必须具有类型HasF或者从HasF导出的类型。这里编译器实际上会把类型参数替换为它的擦除,即T擦除到了HasF,就好像在类型声明中用HasF替换了T一样。

2、擦除的问题

擦除的代价是显著的,泛型不能显示的引用运行时的类型的操作之中,例如转型、instanceof、new表达式。因为所有的类型信息都丢失了。例如:

class Foo<T>{    T var}

那么看起来在创建Foo实例时:Foo<Cat> f = new Foo<Cat>();当进入class Foo<T>内部时,就会发现T会被Object替换,而不是Cat,所以在编写这个类的代码时必须非常注意,它只是一个Object。

3、擦除的边界

public class GenericHolder<T> {  private T obj;  public void set(T obj) { this.obj = obj; }  public T get() { return obj; }  public static void main(String[] args) {    GenericHolder<String> holder =      new GenericHolder<String>();    holder.set("Item");    String s = holder.get();  }}

  在这个例子中,hoider.set()方法里如果传入的不是一个字符串,而是其他类型的参数,编译器会报错,而holder.get()的方法获取到值赋给s时,也不用我们自己来转型,因此,擦除在方法或者类内部移除了有关实际类型的信息,但是会在传递进来的值进行额外的编译期检查,即编译器对set()方法的参数进行检查,而对get()返回的值进行转型也会有编译器自动插入(可以通过javap命令查看)。记住泛型边界即是对象进入和离开方法的地点。

4、擦除的补偿

  正如我们看到,擦除丢失了在泛型代码中执行某些操作的能力。任何需要确切类型信息的操作都将无法操作:

public class Erased<T> {  private final int SIZE = 100;  public static void f(Object arg) {//    if(arg instanceof T) {}          // Error//    T var = new T();                 // Error//    T[] array = new T[SIZE];         // Error//    T[] array = (T)new Object[SIZE]; // Unchecked warning  }}

这里对使用instanceof的使用都失败了,因为类型信息已经被擦除了,但是可以通过显式的传递类型的Class对象来进行补偿。

class Building {}class House extends Building {}public class ClassTypeCapture<T> {  Class<T> kind;  public ClassTypeCapture(Class<T> kind) {    this.kind = kind;  }  public boolean f(Object arg) {    return kind.isInstance(arg);  }  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:truetruefalsetrue

  编译器可以确保类型标签匹配泛型参数

5、通配符 

public class NonCovariantGenerics {  // Compile Error: incompatible types:/*  List<Fruit> flist = new ArrayList<Apple>();  */}

  当我们阅读这段代码时,即不能将一个持有Apple类型的容器赋值给一个持有Fruit类型的容器,但是回头一想会觉得一个持有Fruit的容器确实向上转型为一个持有Fruit的容器,打个比喻:"一个装苹果的篮子属于一个装水果的篮子么",我觉得答案应该是属于的,但是编译器编译这行代码会报错,大概是因为编辑器知道的信息太少了,如果它能像我们这样来推断就是属于的,但是它不知道任何有关这方面的信息,故不能对此进行推断,所以会报错。

 但是有时我们会在两个类型中建立某种类型的向上关系,这是通配符所允许的:

public class GenericsAndCovariance {  public static void main(String[] args) {    // Wildcards allow covariance:    List<? extends Fruit> flist = new ArrayList<Apple>();    // Compile Error: can't add any type of object:    // flist.add(new Apple());    // flist.add(new Fruit());    // flist.add(new Object());    flist.add(null); // Legal but uninteresting    // We know that it returns at least Fruit:    Fruit f = flist.get(0);  }}

这里的"?"表示List可以持有任何类型,只要是Fruit继承下来的都可以,例如flist可以指向new Array<Apple>(),也可以给它赋值为new Array<Orange>(),只要是从Fruit继承下来即可。因为我们不能知道它持有的是什么类型,所以不能调用add方法往里面添加对象(除了null)。但是可以使用get()方法获取对象,因为它至少是Fruit类型的。
  还有一种是超类型通配符,方法是指定<? super MyClass>,甚至可以使用类型参数<? super T>:

public class SuperTypeWildcards {  static void writeTo(List<? super Apple> apples) {    apples.add(new Apple());    apples.add(new Jonathan());    // apples.add(new Fruit()); // Error  }}

  此时Apple是下界,那么往其中添加Apple或者Appple的子类型是安全的,但是此时不能调用get方法,因为你不知道你读出来的类型。
  还有一种就是无界通配符<?>,那么List、List<?>、List<? extends Object>有什么差别呢,实际上它们三个差别极小,相互转换时可能会发出警告,List是原生的,实际上就是List<Object>,即代表可以持有任何类型的组合,而List<?>表示具有某种特定类型的非原生List,只是我们不知道那种类型是什么,而List<? extends Object>可以通过前面的通配符来理解。

public class Wildcards {   // Raw argument:  static void rawArgs(Holder holder, Object arg) {    // holder.set(arg); // Warning:  只有警告    //   Unchecked call to set(T) as a    //   member of the raw type Holder    // holder.set(new Wildcards()); // Same warning    // Can't do this; don't have any 'T':    // T t = holder.get();    // OK, but type information has been lost:    Object obj = holder.get();  }  // Similar to rawArgs(), but errors instead of warnings:  static void unboundedArg(Holder<?> holder, Object arg) {    // holder.set(arg); // Error:holder可以是指向一个持有任何类型的Holder,因此set时,不能判断参数类型是否满足,会报错    //   set(capture of ?) in Holder<capture of ?>    //   cannot be applied to (Object)    // holder.set(new Wildcards()); // Same error    // Can't do this; don't have any 'T':    // T t = holder.get();    // OK, but type information has been lost:    Object obj = holder.get();  }  static <T> T exact1(Holder<T> holder) {    T t = holder.get();    return t;  }  static <T> T exact2(Holder<T> holder, T arg) {    holder.set(arg);    T t = holder.get();    return t;  }  static <T>  T wildSubtype(Holder<? extends T> holder, T arg) {    // holder.set(arg); // Error:    //   set(capture of ? extends T) in    //   Holder<capture of ? extends T>    //   cannot be applied to (T)    T t = holder.get();    return t;  }  static <T>  void wildSupertype(Holder<? super T> holder, T arg) {    holder.set(arg);    // T t = holder.get();  // Error:    //   Incompatible types: found Object, required T    // OK, but type information has been lost:    Object obj = holder.get();  }  /** * @param args */public static void main(String[] args) {    Holder raw = new Holder<Long>();    // Or:    raw = new Holder();    Holder<Long> qualified = new Holder<Long>();    Holder<?> unbounded = new Holder<Long>();    Holder<? extends Long> bounded = new Holder<Long>();    Long lng = 1L;    rawArgs(raw, lng);    rawArgs(qualified, lng);    rawArgs(unbounded, lng);    rawArgs(bounded, lng);    unboundedArg(raw, lng);    unboundedArg(qualified, lng);    unboundedArg(unbounded, lng);    unboundedArg(bounded, lng);    // Object r1 = exact1(raw); // Warnings:    //   Unchecked conversion from Holder to Holder<T>    //   Unchecked method invocation: exact1(Holder<T>)    //   is applied to (Holder)    Long r2 = exact1(qualified);    Object r3 = exact1(unbounded); // Must return Object    Long r4 = exact1(bounded);    // Long r5 = exact2(raw, lng); // Warnings:    //   Unchecked conversion from Holder to Holder<Long>    //   Unchecked method invocation: exact2(Holder<T>,T)    //   is applied to (Holder,Long)    Long r6 = exact2(qualified, lng);    // Long r7 = exact2(unbounded, lng); // Error:    //   exact2(Holder<T>,T) cannot be applied to    //   (Holder<capture of ?>,Long)    // Long r8 = exact2(bounded, lng); // Error:    //   exact2(Holder<T>,T) cannot be applied    //   to (Holder<capture of ? extends Long>,Long)    // Long r9 = wildSubtype(raw, lng); // Warnings:    //   Unchecked conversion from Holder    //   to Holder<? extends Long>    //   Unchecked method invocation:    //   wildSubtype(Holder<? extends T>,T) is    //   applied to (Holder,Long)    Long r10 = wildSubtype(qualified, lng);    // OK, but can only return Object:   /*compile error xukun    *  Object r11 = wildSubtype(unbounded, lng);*/    Long r12 = wildSubtype(bounded, lng);    // wildSupertype(raw, lng); // Warnings:    //   Unchecked conversion from Holder    //   to Holder<? super Long>    //   Unchecked method invocation:    //   wildSupertype(Holder<? super T>,T)    //   is applied to (Holder,Long)    wildSupertype(qualified, lng);    // wildSupertype(unbounded, lng); // Error:    //   wildSupertype(Holder<? super T>,T) cannot be    //   applied to (Holder<capture of ?>,Long)    // wildSupertype(bounded, lng); // Error:    //   wildSupertype(Holder<? super T>,T) cannot be    //  applied to (Holder<capture of ? extends Long>,Long)  }}


参考自《Java编程思想》

0 0