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