软件构造-泛型的不可协变性

泛型是实现参数化多态的一种形式,泛型通过使用通配符<?>来实现基于某个特定类型的类,这个特定的类型会在编译时被填充,会通过“类型擦除”来实现,例如:

定义了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提供的通配符,一定程度上突破了泛型不可协变性的限制,例如:

  1. // collection1可以存放任何类型  
  2. Collection<?> collection1 = new ArrayList<String>();  
  3. collection1 = newArrayList<Integer>();  
  4. collection1 = newArrayList<Object>();  
  5.    
  6. //collection3表示它可以存放Number或Number的子类    
  7. Collection<? extends Number> collection3 = null;  
  8. collection3 = newArrayList<Number>();  
  9. collection3 = newArrayList<Double>();  
  10. collection3 = newArrayList<Long>();  
  11.    
  12. //collection4表示它可以存放Integer或Integer的父类    
  13. Collection<? super Integer> collection4 = null;  
  14. collection4 = newArrayList<Object>();  

P.S.:以上之所以都是使用ArrayList做例子,是因为Java不支持泛型数组,请在需要时自觉使用ArrayList

posted @ 2022-06-13 15:43  DDDaily  阅读(38)  评论(0编辑  收藏  举报