使用泛型机制编写的程序代码要比哪些杂乱地使用Object变量,然后在进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合类。
1 为什么要使用泛型程序设计
泛型程序设计
(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。
1.1 类型参数的好处
不使用泛型的问题:
- 当获取一个值时必须进行强制类型转换。
- 操作时没有错误检查。
泛型提供的解决方案类型参数
(type parameters),使得程序具有更好的可读性和安全性。
如ArrayList,声明时即可知道数据类型,并且添加数据时有检查,可以避免插入错误类型的数据。
1.2 谁想成为泛型程序员
泛型程序员的任务就是预测出所用类的未来可能有的所有用途。
通配符类型
(wildcard type),可以帮助库的构建者编写出尽可能灵活的方法。
2 定义简单泛型类
一个泛型类
(generic class)就是一个或多个类型变量的类。
public class Pair<T> {
private T first;
private T second;
public Pair() { first = null;second = null; }
public Pair(T first, T second) { this.first = first;this.second = second; }
public T getFirst() { return first; }
public void setFirst(T first) { this.first = first; }
public T getSecond() { return second; }
public void setSecond(T second) { this.second = second; }
}
Pair类引入了一个类型变量T,用<>括起来,并放在类型后面。泛型类可以有多个类型变量。
类定义中的 类型变量 指定 方法的返回类型 以及 域和局部变量的类型。
3 泛型方法
定义一个带有类型参数的简单方法:
class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
4 类型变量的限定
有时,类或者方法需要对类型变量加以约束。解决方案是将T限制为实现某个接口。
public static <T extends Comparable> T min(T[] a)...
一个类型变量或通配符可以有多个限定:
T extends Comparable & Serializable
5 泛型代码和虚拟机
虚拟机没有泛型类型对象--所有对象都属于普通类。
5.1 类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型
(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除
(erased)类型变量,并替换为限定类型(无限定的变量用Object)。
5.2 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
5.3 翻译泛型方法
类型擦除也会出现在泛型方法中。
类型擦除与多态发生冲突。解决这个问题,编译器会生成一个桥方法(bridge method)。
Java泛型转换的事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保存多态。
- 为保持类型安全性,必要时插入强制类型转换。
5.4 调用遗留代码
设计Java泛型类型时,主要目标是运行泛型代码和遗留代码之间能够互操作。
6 约束与局限性
Java泛型使用时要考虑一些限制,大多数限制都是由类型擦除引起的。
(todo暂时只有介绍,没有原因说明)
6.1 不能用基本类型实例化类型参数
没有Pair<double>,只有Pair<Double>。
原因是类型擦除之后,Pair类含有Object类型的域,而Object不能存储double值。
6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
if (a instanceof Pair<String>) //error
Pair<String> p = (Pair<String>) a;//Warning
Pair<T>的gatClass将返回Pair.class
6.3 不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10];//error
如果需要收集参数化类型对象,使用ArrayList<Pair<String>>。
6.4 Varargs警告
@SuppressWarnings("unchecked")和@SafeVarargs抑制警告。
6.5 不能实例化类型变量
不能使用像new T(...),new T[...]或T.class这样的表达式中的类型变量。
6.6 不能构造泛型数组
public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; ...}//error
6.7 泛型类的静态上下文中类型变量无效
public class Singleton<T>{
private static T instance; //error
public static T getInstance(){...} //error
}
6.8 不能抛出或捕获泛型类的实例
public class Problem<T> extends Exception {/**...**/}//error
6.9 可以消除对受查异常的检查
6.10 注意擦除后的冲突
public boolean equals(T value){...}//error
方法擦除equals(T)就是equals(Object),与Object.equals方法冲突。
7 泛型类型的继承规则
无论S与T有什么关系,通常Pair<S>与Pair<T>没有什么联系。
8 通配符类型
8.1 通配符的概念
通配符类型中,运行类型参数变化。如Pair<? extends Employee>,表示任何泛型Pair类型。
8.2 通配符的超类型限定
通配符还可以指定一个超类型限定,? super Manager
。
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
public static <T extends Comparable<? super T>> T min(T[] a)
有可能被声明为使用类型T的对象,也有可能使用T的超类型。
8.3 无限定通配符
还可以使用无限定的通配符,如Pair<?>。
Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始Pair类的方法。它对于许多简单操作非常有用,比如hasNulls(Pair<?> p)。
8.4 通配符捕获
9 反射和泛型
9.1 泛型Class类
9.2 使用Class参数进行类型匹配
9.3 虚拟机中的泛型类型信息