第八章 泛型程序设计
从Java程序设计语言1.0版发布以来,变化最大的部分就是泛型。
泛型正是我们需要的程序设计手段。使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有用。
8.1 为什么要使用泛型程序设计
泛型程序设计(Generic programming)意味着编写的代码可以被很多不同类型的对象所重用。
8.1.1 类型参数的好处
泛型提供了一个更好的解决方案:类型参数(typeparameters)。ArrayList类有一个类型参数用来指示元素的类型:
ArrayList<String> files = new ArrayList<>();
省略的类型可以从变量的类型推断得出。
类型参数的魅力在于:使得程序具有更好的可读性和安全性。
8.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 T getSecond(){
return second;
}
}
Pair类引入了一个类型变量T,用尖括号(< >)括起来,并放在类名的后面。泛型类可以有多个类型变量。
换句话说,泛型类可看作普通类的工厂。
下面的测试方法对泛型类进行测试
public class PairTest1 {
public static void main(String[] args) {
String[] words = {
"ff", "fdf", "GTR"};
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min = " + mm.getFirst());
System.out.println("max = " + mm.getSecond());
}
}
class ArrayAlg
{
public static Pair<String> minmax(String[] a)
{
if (a == null || a.length == 0)
return null;
String min = a[0];
String max = a[0];
for (int i = 0; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
8.3 泛型方法
前面已经介绍了如何定义一个泛型类。实际上,还可以定义一个带有类型参数的简单方法。
这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。
注意,类型变量放在修饰符(这里是public static)的后面,返回类型的前面。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
public static <T> T getMiddle(T ... a)
{
return a[a.length / 2];
}
// 调用
String middle = ArrayAlg.<String>getMiddle("John", "Q", "DGG");
8.5 泛型代码和虚拟机
虚拟机没有泛型类型对象——所有对象都属于普通类。
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。
例如,Pair的原始类型如下所示:
public class Pair {
private Object first;
private Object second;
public Pair()
{
first = null;
second = null;
}
public Pair(Object first, Object second)
{
this.first = first;
this.second = second;
}
public Object getFirst(){
return first;
}
public Object getSecond(){
return second;
}
}
因为T是一个无限定的变量,所以直接用Object替换。结果是一个普通的类,就好像泛型引入Java语言之前已经实现的那样。
8.6 约束与局限性
不能用基本类型实例化类型参数
因此,没有Pair,只有Pair。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double值。
运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
不能实例化类型变量
不能使用像new T(…), new T[…]或T.class这样的表达式中的类型变量。
不能构造泛型数组
就像不能实例化一个泛型实例一样,也不能实例化数组。
8.8 通配符类型
8.8.1 通配符概念
通配符类型中,允许类型参数变化。例如,通配符类型
Pair <? extends Employee>
表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair,但不是Pair。
8.8.2 通配符的超类型限定
通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定(supertype bound)
下面的方法将可以接受任何适当的Pair
public static void mimm(Manager[] a, Pair<? super Manager> result)
{
}
8.9 反射和泛型
反射允许你在运行时分析任意的对象。如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除。
在下面的小节中,可以了解利用反射可以获得泛型类的什么信息。
8.9.1 泛型Class类
现在,Class类是泛型的。例如,String.class实际上是一个Class类的对象(事实上,是唯一的对象)。
类型参数十分有用,这是因为它允许Class方法的返回类型更加具有针对性。下面Class中的方法就使用了类型参数:
newInstance方法返回一个实例,这个实例所属的类由默认的构造器获得。它的返回类型目前被声明为T,其类型与Class描述的类相同,这样就免除了类型转换。如果给定的类型确实是T的一个子类型,cast方法就会返回一个现在声明为类型T的对象,否则,抛出一个BadCastException异常。