泛型的不可协变性
原因
泛型是实现参数化多态的一种形式,泛型通过使用通配符<?>来实现基于某个特定类型的类,这个特定的类型会在编译时被填充,会通过“类型擦除”来实现,例如:
定义了Set的泛型类,我们声明了 Set s1 = new Set(); 在编译阶段,编译器将其转化成为了真正的类Set_Integer,类中的T类型均会被替换为Integer,这也就是类型擦除的含义。
我们在使用泛型时,总会联想到有关继承的协变性或者逆变性,然而泛型实际上是不可协变的,也就是说不存在所谓的继承关系,这其中就和类型擦除的概念相关。
不妨以数组的协变性来举例:我们知道,如果类A是类B的子类,那么A[]是B[]的子类。我们可以写 Object[] obj = new String[]{};
这样的用法会成功通过编译器的检查。而且即便在obj中存放了非string对象,也会在运行时才报异常,静态检查阶段不会抛出异常,Object是String的父类,所以这是合法的。
然而当我们写 ArrayList obj = new ArrayList(); 编译却无法通过,这就是由于泛型的不可协变性的影响:编译器会对泛型进行类型擦除,从而生成新的类 ArrayList_Object 和 ArrayList_Integer,这样的两个类,不存在继承关系,不在继承树上有确切的父子关系,编译器无法new一个obj对象,自然也就出现了异常。
解决办法
那么怎样解决这个问题?我们可以利用通配符<?>中的关键字extends和super,例如 ArrayList<? extends Object> list = new ArrayList; 这样的语句是合法的,Java提供的通配符,一定程度上突破了泛型不可协变性的限制,例如:
// collection1可以存放任何类型
Collection<?> collection1 = new ArrayList<String>();
collection1 = newArrayList<Integer>();
collection1 = newArrayList<Object>();
//collection3表示它可以存放Number或Number的子类
Collection<? extends Number> collection3 = null;
collection3 = newArrayList<Number>();
collection3 = newArrayList<Double>();
collection3 = newArrayList<Long>();
//collection4表示它可以存放Integer或Integer的父类
Collection<? super Integer> collection4 = null;
collection4 = newArrayList<Object>();