Java泛型中的擦除

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:
true
true
false
true

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

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编程思想》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值