12.4 通配符
在实例化一个泛型类型时,需要为该泛型类型中的形式类型参数指定具体的类型。在某些情况下,所要使用的具体类型是确定的。比如声明一个只包含String类型对象的List接口,直接使用List<String>即可。在另外一些情况下,并不需要对具体的类型做出明确的限定,只需要这些类型满足一定的条件即可。比如对一组对象进行排序的方法,并不要求对象的类型是固定的某个类型,只要这些类型实现Comparable接口,就可以使用Comparable接口的compareTo方法来确定顺序。这时要表示的不是单个的类型,而是一组类型的集合。通配符的作用是描述一组类型,而其中包含的类型要满足的条件由通配符的上界或下界来声明。通配符用“?”表示,分成无界和有界两种。无界通配符只用“?”来表示,代表的是包含所有类型的集合;有界通配符可以有上界或下界,分别用“?extends”和“?super”来表示,但不能同时有上界和下界。上界通配符代表的是包含上界类型及其子类型的集合,而下界通配符代表的是包含下界类型及其父类型的集合。
通配符的含义是表示一组类型的集合,不同于单个具体的类型。为了表示这种类型,编译器在内部使用通配符捕获(wildcard capture)类型的方式。通配符捕获类型是一种特殊的类型,可以表示通配符所代表的类型集合中的任意类型。编译器在涉及通配符的类型比较中使用通配符捕获类型来进行处理。通配符捕获类型与所有的具体类型都是不兼容的,因为一个类型的集合无法与单个具体类型兼容。通配符捕获类型只能与其他的通配符捕获类型相兼容。例如,对于一个类型声明为“List<?extends Number>”的变量,编译器在内部实际使用的通配符捕获类型是“List<capture#1-of?extends Number>”。由于类型不兼容,代码清单12-12中use方法的赋值操作会出现编译错误。编译器给出的错误信息是“Type mismatch:cannot convert from List<capture#1-of?extends Number>to List<String>”,其含义是赋值操作试图把“List<capture#1-of?extends Number>”类型的对象转换成“List<String>”类型,由于这两个类型不兼容,类型转换操作无法进行。
代码清单12-12 说明通配符捕获类型的示例
public class Wildcard{
public List<?extends Number>createList(){
return new ArrayList<>();
}
public void use(){
List<String>list=createList();//编译错误
}
}
获取一个包含通配符的参数化类型的对象引用之后,对这个对象引用所能做的操作是非常有限的。例如,对于一个类型为List<?>的对象引用,当尝试调用add方法向列表中添加任何类型的对象时,都会出现编译错误,因为编译器无法确定List<?>类型对象中实际的类型,这个add方法的调用有可能是不安全的,因此编译器禁止了这样的行为。但是在很多情况下,只能使用含通配符的参数化类型作为引用的类型。例如,Class类中的很多方法的返回值都是Class<?>类型的。以Class类中的getInterfaces方法为例,该方法用来介绍获取当前类所实现的接口或当前接口所继承的接口。该方法的返回值类型是Class<?>[]。使用其他更加具体的类型作为返回值类型都是不正确的,因为该方法的返回值的实际类型可能是任何类型。在使用包含通配符的参数化类型的对象引用时,通常需要提供额外的类型信息来方便对该对象的使用。
下面通过一个示例来说明如何提供额外的类型信息。在一个程序中包含了某个接口的多个不同实现类,程序根据相关的配置信息来选择使用的具体实现类。代码清单12-13给出了一个创建该接口实现对象的示例。在调用create方法时,需要提供表示接口类型的Class类的对象作为额外的参数。找到具体实现类的类名之后,使用Class类的forName方法来加载该类。由于forName方法的返回值类型是Class<?>,因此通过由参数给出的接口类型的cast方法把创建出来的对象转换成正确的类型。
代码清单12-13 为包含通配符的参数化类型提供类型信息
public class ObjectFactory{
public static<T>T create(Class<T>interfaceType)throws Exception{
String className=searchForClassName();
Class<?>clazz=Class.forName(className);
return interfaceType.cast(clazz.newInstance());
}
private static String searchForClassName(){
return"";//省略查找逻辑
}
}
在上面的示例中,具体的类型信息由create方法的调用者来提供,这就保证了create方法的返回值是调用者所期望的类型。