第12章 泛型程序设计
本章内容:
* 为什么要使用泛型程序设计
* 定义简单泛型类
* 泛型方法
* 类型变量的限定
* 泛型代码和虚拟机
* 约束与局限性
* 泛型类型的继承规则
* 通配符类型
* 反射和泛型
- 使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。
12.1 为什么要使用泛型程序设计
- 泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。
12.2 定义简单泛型类
- 一个泛型类(generic class)就是具有一个或多个类型变量的类。
- 类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时还可以用临近的字母U和S)表示“任意类型”。
- 用具体的类型替换类型变量就可以实例化泛型类型。
- 泛型类可看作普通类的工厂。
12.3 泛型方法
- 泛型方法的类型变量放在修饰符(如public static)的后面,返回类型的前面。
- 泛型方法可以定义在普通类中,也可以定义在泛型类中。
- 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型。在大多数情况下,方法调用中可以省略具体的类型。
12.4 类型变量的限定
<T extends BoundingType>
表示T应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是借口。选择关键字extends的原因是更接近子类的概念。- 一个类型变量或通配符可以有多个限定。限定类型用“&”分隔,而逗号用来分隔类型变量。
- 在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。
12.5 泛型代码和虚拟机
- 虚拟机没有泛型类型对象-所有对象都属于普通类。在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为在1.0虚拟机上运行的类文件!这个向后兼容性在Java泛型开发的后期被放弃了。
- 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。
- 原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Object替换。
- 为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾。
12.5.1 翻译泛型表达式
- 当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
- 当存取一个泛型域时也要插入强制类型转换。
12.5.2 翻译泛型方法
- 有关Java泛型转换的事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态。
- 为保持类型安全性,必要时插入强制类型转换。
12.5.3 调用遗留代码
- 设计Java泛型类时,主要目标是允许泛型代码和遗留代码之间能够互操作。
- @SuppressWarnings(“unchecked”)关闭对代码的检查。
12.6 约束与局限性
12.6.1 不能用基本类型实例化类型参数
- 不能用类型参数代替基本类型。
12.6.2 运行时类型查询只适用于原始类型
- 虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始数据。
- 无论何时使用instanceof或涉及泛型类型的强制类型转换表达式都会看到一个编译器警告。
- getClass方法总是返回原始类型。
12.6.3 不能创建参数化类型的数组
Pair<String>[] table=new Pair<String>[10];//error
- 不允许创建参数化类型的数组,而声明类型为Pair[]的变量仍是合法的。不过不能用
new Pair<String>[10]
初始化这个变量。 - 如果需要收集参数化类型对象,只有一种安全而有效的方法:使用ArrayList:
ArrayList<Pair<String>>
。
12.6.4 Varargs警告
- 向参数个数可变的方法传递一个泛型类型的实例:
public static <T> void addAll(Collection<T> coll,T... ts)
{
for(t:ts) coll.add(t);
}
只会得到一个警告,而不是错误。
可以采用两种方法来抑制这个警告。一种方法是为包含addAll调用的方法增加标注@SuppressWarnings(“unchecked”)。或者在Java SE 7中,还可以用@SafeVarargs直接标注addAll方法。
对于只需要读取参数数组元素的所有方法,都可以使用这个标注。
12.6.5 不能实例化类型变量
- 不能使用像new T(…),new T[…]或T.class这样的表达式中的类型变量。
- 不能构造泛型数组。
- 如果数组仅仅作为一个类的私有实例域,就可以将这个数组声明为Object[],并且在获取元素时进行类型转换。
12.6.6 泛型类的静态上下文中类型变量无效
- 不要在静态域或方法中引用类型变量。
12.6.7 不能抛出或捕获泛型类的实例
- 泛型类扩展Throwable都是不合法的。
- catch子句中不能使用类型变量。
- 在异常规范中使用类型变量时允许的。
- Java异常处理的一个基本原则是,必须为所有已检查异常提供一个处理器。不过可以利用泛型消除这个限制。关键在于以下方法:
@SuppressWarnings("unchecked")
public static <T extends Thowable> void throwAs(Throwable e)throws T
{
throw (T)e;
}
- 通过使用泛型类、擦除和@SuppressWarnings标注,就能消除Java类型系统的部分基本限制。
12.6.8 注意擦除后的冲突
- 泛型规范原则:“要想支持擦除的转换,就需要强制限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一个接口的不同参数化。”
12.7 泛型类型的继承规则
- 永远可以将参数化类型转换为一个原始类型。
- 泛型类可以扩展或实现其他的泛型类。
12.8 通配符类型
- 固定的泛型系统使用并不是很好,可以使用“通配符类型”解决。
12.8.1 通配符的超类型限定
- 通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定(supertype bound)。super。
- 带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
12.8.2 无限定通配符
- 还可以使用无限定通配符,
Pair<?>
。Pair<?>
和Pair
本质的不同在于:可以用文艺Object对象调用原始的Pair类的setObject方法。
12.8.3 通配符捕获
- 通配符不是类型变量,因此,不能在编写代码中使用“?”作为一种类型。
- 通配符捕获只有在由许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。
12.9 反射和泛型
- Class类是泛型的。String.class实际上是一个
Class<String>
类的对象(事实上,是唯一的对象)。 - java.lang.Class 1.0
- T newInstance() 5.0
返回默认构造器构造的一个新实例。 - T cast(Object obj) 5.0
如果obj为null或有可能转换为类型T,则返回obj;否则抛出BadCastException异常。 - T[] getEnumConstants() 5.0
如果T是枚举类型,则返回所有值组成的数组,否则返回null。 - Class<? super T> getSuperClass() 5.0
返回这个类的超类。如果T不是一个类或Object类,则返回null。 - Constructor getConstructor(Class… parameterTypes) 5.0
- Constructor getDeclaredConstructor(Class… parameterTypes) 5.0
获得公有的构造器,或带有给定参数类型的构造器。
- java.lang.reflect.Constructor 1.1
- T newInstance(Object… parameters) 5.0
返回用指定参数构造的新实例。
12.9.1 使用Class参数进行类型匹配
12.9.2 虚拟机中的泛型类型信息
- 包含在类文件中,让泛型反射可用的类型信息与旧的虚拟机不兼容。
- 为了表达泛型类型声明,Java SE 5.0在java.lang.reflect包中提供了一个新的接口Type。这个接口包含下列子类型:
- Class类,描述具体类型。
- TypeVariable接口,描述类型变量(如T extends Comparable<? super T>)。
- WildcardType接口,描述通配符(如? super T)。
- ParameterizedType接口,描述泛型类或接口类型(如Comparable<? super T>)。
- GenericArrayType接口,描述泛型数组(如T[])。
注意,最后4个子类型时接口,虚拟机将实例化实现这些接口的适当的类。
- java.lang.Class 1.0
- TypeVariable[] getTypeParameters() 5.0
如果这个类型被声明为泛型类型,则获得泛型类型变量,否则获得一个长度为0的数组。 - Type getGenaricSuperclass() 5.0
获得被声明为这一类型的超类的泛型类型;如果这个类型是Object或不是一个类类型(class type),则返回null。 - Type[] getGenericInterfaces() 5.0
获得被声明为这个类型的接口的泛型类型(以声明的次序),否则,如果这个类型没有实现接口,返回长度为0的数组。
- java.lang.reflect.Method 1.1
- TypeVariable[] getTypeParameters() 5.0
如果这个方法被声明为泛型方法,则获得泛型类型变量,否则返回长度为0的数组。 - Type getGenericReturnType() 5.0
获得这个方法被声明的泛型返回类型。 - Type[] getGenericParameterTypes() 5.0
获得这个方法被声明的泛型参数类型。如果这个方法没有参数,返回长度为0的数组。
- Java.lang.reflect.TypeVariable 5.0
- String getName()
获得类型变量的名字。 - Type[] getBounds()
获得类型变量的子类限定,否则,如果该变量无限定,则返回长度为0的数组。
- Java.lang.reflect.WildcardType 5.0
- Tyep[] getUpperBounds()
获得这个类型变量的子类(extends)限定,否则,如果没有子类限定,则返回长度为0的数组。 - Type[] getLowerBounds()
获得这个类型变量的超类(super)限定,否则,如果没有超类限定,则返回长度为0的数组。
- Java.lang.reflect.ParameterizedType 5.0
- Type getRawType()
获得这个参数化类型的原始类型。 - Type[] getActualTypeArguments()
获得这个参数化类型声明时所使用的类型参数。 - Type getOwnerType()
如果是内部类型,则返回其外部类型,如果是一个顶级类型,则返回null。
- Java.lang.reflect.GenericArrayType 5.0
- Type getGenericComponentType()
获得声明该数组类型的泛型组合类型。