12.6 类型系统
在Java语言中,类型系统描述了不同类型之间的转换关系。如果一个类型是另外一个类型的子类型,那么从子类型到父类型的转换是自动进行的,而从父类型到子类型的转换需要在代码中显式进行,这是因为从父类型到子类型的转换可能是不安全的,需要由开发人员显式进行。在泛型被引入之前,Java中的父类型和子类型的关系主要通过类继承和接口实现机制来声明。如果类或接口A继承自类或接口B,则A是B的子类型;如果类C实现了接口D,则C是D的子类型。泛型的引入对Java语言中的类型系统产生了比较大的影响。这是因为泛型类型的实例化形式中包含了所使用的实际类型,这些类型之间也可以有父子类型关系。这相当于把类型系统从之前的一维结构扩展为二维结构:一个维度是泛型类型本身,另外一个维度是参数化类型中的实际类型。比如ArrayList类实现了List接口,因此ArrayList类是List接口的子类型。把泛型考虑在内,对于ArrayList<Number>和List<Number>、ArrayList<Integer>和ArrayList<Number>,以及ArrayList<Integer>和List<Number>这三组参数化类型,虽然ArrayList类是List接口的子类型,Integer类也是Number类的子类型,但是这三组类型之间并不一定存在子类型与父类型的关系。
先看两种简单的情况。当两个参数化类型的实际类型完全相同时,两个类型的父子类型关系取决于泛型类型本身的父子关系。对于ArrayList<Number>和List<Number>这两个参数化类型,它们的实际类型都是Number,因此两者的父子类型关系取决于ArrayList类和List接口之间的父子类型关系,从而可以得出ArrayList<Number>是List<Number>的子类型。这条规则对于包含通配符的情况也是适用的,所以ArrayList<?extends Number>是List<?extends Number>的子类型。
如果泛型类型相同,在实例化时使用的是不包含通配符的两个不同的具体类型,则这两个类型之间不存在任何父子类型的关系。例如,ArrayList<Integer>和ArrayList<Number>两个类型之间不存在父子类型的关系。这一点是比较容易理解错误的地方。一般的理解是,既然Integer类是Number类的子类型,那么包含一组Integer类的对象的列表,同时也应该是包含一组Number类的对象的列表。实际上,如果允许这种父子类型关系的存在,那么会产生类型安全问题。
代码清单12-18给出了具体的示例。类ModifyList中的modify方法的参数类型是ArrayList<Number>,在changeList方法中试图把一个ArrayList<Integer>类型的对象传递给modify方法,但是编译器并不允许这么做。假设这种传递方式是合法的,在modify方法中,由于参数类型是ArrayList<Number>,因此该方法可以向参数对象中添加任何Number类及其子类型的对象,包括与Integer类型不同的Float或Double类的对象。而变量list的使用者所期望的是ArrayList类的对象中只包含Integer类型的对象,这样就出现了类型安全问题,所以编译器禁止这种使用方式。
代码清单12-18 ArrayList<Number>和ArrayList<Integer>无法兼容的说明示例
public class ModifyList{
public void modify(ArrayList<Number>list){
list.add(1.0f);//添加一个Float类型的对象
}
public void changeList(){
ArrayList<Integer>list=new ArrayList<Integer>();
list.add(3);
modify(list);//编译错误
Integer value=list.get(1);
}
}
上面只介绍了在对泛型类型实例化时使用的类型完全相同的情况,如果考虑到类型之间的转换关系和通配符的使用,则情况更加复杂。在参数化类型的实际类型这个维度上,父子类型的关系只存在于包含通配符的情况。通配符代表的是一组类型,而不是单一的具体类型。当两个类型都是具体类型时,会遇到之前介绍的类似ArrayList<Integer>和ArrayList<Number>类型的问题,所以至少要有一个类型是包含通配符的类型,才可能存在父子类型的关系。实际类型维度上的父子类型关系可以看成是所表示的类型集合之间的包含关系。如果一个类型集合是另外一个类型集合的子集,则在对应的相同泛型类型的实例化形式中,前者是后者的子类型。实际上,单一类型也可以看成是包含单个类型元素的集合。在两个类型不相同的情况下,这两个单一元素集合中不可能有其中一个是另外一个的子集合,因此也不可能存在父子类型的关系。
在包含通配符时,无界和有界通配符的情况有所不同。对于无界通配符来说,使用无界通配符的参数化类型是所有该泛型类型的其他实例化形式的父类型,因为无界通配符表示的是所有可能的类型的集合。从集合论的角度来说,无界通配符表示的是全集,包含所有其他的集合。例如,List<?>是所有List泛型类的实例化形式的父类型,List<String>、List<?extends Number>和List<?super Integer>等都是List<?>的子类型。
使用有界通配符的情况取决于具体使用的上界或下界的类型。上界通配符表示的是上界类型及其子类型的集合。两个包含上界通配符的类型对应的集合之间的关系取决于上界类型本身是否存在父子类型关系。例如,“?extends A”和“?extends B”之间的关系取决于类型A和B之间的关系,这是因为如果A是B的子类型,那么包含在“?extends A”对应的集合中的类型肯定包含在“?extends B”对应的集合中。由于Integer类是Number类的子类型,因此“?extends Integer”是“?extends Number”的子类型,进而可知List<?extends Integer>是List<?extends Number>的子类型。上界通配符的情况对于下界通配符也是适用的,只不过父子类型关系要颠倒过来。两个包含下界通配符的类型对应的集合之间的关系同样取决于下界类型本身是否存在父子类型关系。对于“?super A”和“?super B”来说,当B是A的子类型时,“?super A”是“?super B”的子类型,因此“?super Number”是“?super Integer”的子类型,进而可知List<?super Number>是List<?super Integer>的子类型。
有些泛型类型包含多个形式类型参数,在实例化时可以使用对应的多个通配符。对于每个实际类型都包含通配符的情况,对每个类型都应用上面提到的规则来进行判断。如果一个参数化类型声明中的所有类型都是另外一个参数化类型的对应类型的子类型,则前者是后者的子类型。比如,类型Map<Integer,?extends Number>是Map<?extends Number,?>的子类型,这是因为Map<Integer,?extends Number>的第一个实际类型Integer包含在“?extends Number”所表示的类型集合中,第二个类型参数“?extends Number”所表示的集合是“?”所表示的集合的子集。
在参数化类型的单个类型中也可以多次使用通配符,如在List<?extends List<?extends Number>>类型声明中使用了两个上界通配符作为实际的类型。对于这种情况,对父类型与子类型的判断比较复杂。基本的思路是按照递归的方式依次进行判断,从最内层的类型开始进行判断。比如,对于List<?extends List<Integer>>和List<?extends List<?extends Number>>这两个类型,具体的判断过程是:Integer类型是“?extends Number”的子类型,进而可知List<Integer>是List<?extends Number>的子类型。而List<Integer>和List<?extends Number>作为通配符的上界出现,因此List<?extends List<Integer>>是List<?extends List<?extends Number>>的子类型。
最后一种特殊的父子类型的关系与原始类型有关。一个泛型类型的所有实例化形式是其对应的原始类型的子类型,如List<String>和List<?extends Number>都是List的子类型。这么设计的目的是为了与不使用泛型的遗留代码进行交互。
因为父子类型关系是传递的,所以在进行复杂类型的判断时,可以根据上面的规则从简单的情况开始考虑。比如,在判断ArrayList<Integer>和Collection<?extends Number>之间的关系时,先考虑到ArrayList<Integer>是Collection<Integer>的子类型,而Collection<Integer>是Collection<?extends Number>的子类型,所以ArrayList<Integer>是Collection<?extends Number>的子类型。