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