本文参考《Java核心技术 II 高级特性》有关章节
一. 为什么要使用泛型程序设计
泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。例如,我们不希望为聚集String和File对象分别设计不同的类。实际上根本不需要那么做,因为一个ArrayList类可以聚集任何类型的对象。这是一个泛型程序设计的实例。
在Java SE5.0之前,Java的泛型程序设计是用继承实现的,ArrayList类只负责维护一个Object引用的数组,看以下的代码:
public class ArrayList {
public Object get(int i){return i;}
public void add(Object o){ }
private Object[] elementData;
}
实例化一个以上的ArrayList类对象:
ArrayList files = new ArrayList();
String filename = (String)m.get(0); //获取一个值时必须强制类型转换
//files.add(new File("...")); //由于这里没有错误检查,可以向数组列表中添加任何类的对象
对于以上代码中的调用,编译和运行都不会出错。然而在其他地方,如果将get的结果强制类型转换为String类型,就会产生一个错误。
而现在泛型提供了一个更好的解决方案:类型参数(type parameters).ArrayList类有一个类型参数用来指示元素的类型:
ArrayList<String> files = new ArrayList<String>();
这使得代码具有更好的可读性。人们一看就知道这个数组列表中包含的是String类型对象。
而编译器也可以很好的利用这个信息。当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String,而不是Object:
String filename = files.get(0);
编译器还将知道ArrayList<String>中的add方法有一个类型为String 的参数。这将比使用Object 类型的参数更加安全一点。现在编译器可以进行检查,避免插入错误类型的对象。比如:
//files.add(new File("...")); //这里的这行代码是无法通过编译的。
出现编译错误比类在运行时出现强制类型转换的异常要好的多。类型参数的魅力在于:使得程序具有更好的可读性和安全性。
二. 简单泛型类的定义
1. 简单泛型类的定义
一个泛型类(generic class)就是具有一个或者多个类型变量的类。 看下面的简单的泛型类Pair例子:
public class Pair<T>
{
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; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
}
Pair类引入一个类型变量T,用尖括号(<>)括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义Pair类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,U>{...}
类定义中的类型变量指定方法的返回类型以及域和局部变量的类型,例如:
private T first;
注意:类型变量使用大写形式,而且比较短。在Java类库中使用变量E表示集合元素类型,K表示关键字类型,V表示值的类型,T表示任意类型。
2. 简单泛型类的实例化
用具体的类型替换类型参数就可以实例化泛型类型,比如:
Pair<String> x = ..
使用泛型类中的构造方法和成员方法是一定要注意类型参数的一致性,比如:
new Pair<T>(T a);
new Pair<String>(String a); //若a的类型和T或者具体的String不一致时需要强制类型转换
3. 例子(泛型返回类型方法)
下面的代码列举了使用一个在普通类ArrayAlg中返回类型为泛型的方法minmax,用其来获取字符串数组words中的字符串字典排序。
Pair<T>泛型类:
public class Pair<T>
{
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; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
private T first;
private T second;
}
PairTest1普通类:
public class PairTest1
{
public static void main(String[] args)
{
String[] words = { "Mary", "had", "a", "little", "lamb" };
Pair<String> mm = ArrayAlg.minmax(words); //这里mm的类型必须设置为Pair<String>,而不可以为Pair<T>
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 = 1; 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<String>(min, max);
}
}
三. 泛型方法
1. 泛型方法的定义
上面已经介绍了如何定义一个泛型类。实际上,还可以定义一个带有类型参数的简单方法。看下面的模板:
public static <T> T getMiddle(T[] b) {
return b[b.length/2];
}
这个方法是用在普通类中定义的,而不是用在泛型类中定义。但是,这是一个泛型方法,这一点可以从尖括号和类型变量中看出来。
需要注意的是,类型变量放在修饰符(这里是public static)的后面,返回类型的前面。当然泛型方法可以定义在普通类中,也可以定义在泛型类中。
2. 泛型方法的调用
当调用一个泛型方法时,在方法名前面的方括号中放入具体类型:
String[] names = { "John", "Q.", "public" };
String middle = ArrayAlg.<String>getMiddle(names);
在这种情况下,方法调用中可以省略掉<String>类型参数。编译器有足够的信息来判断出多调用的方法。它用的names的类型(即String[ ]类型)与泛型类型T[ ]进行匹配,并且判断出T一定是String。也就是说以下对于泛型方法的调用的代码也是对的:
String middle = ArrayAlg.getMiddle(names);
3. 泛型方法例子
下面的代码列举了一个在普通类ArrayAlg中泛型方法getMiddle()的使用,其返回一个字符串数组的中间字符串。
Pair<T>泛型类
public class Pair<T>
{
//以下可看作是构造方法
public Pair() { first = null; second = null; }
public Pair(T middle){ this.middle = middle;}
//以下可以看作是泛型类的成员方法
public T getMiddleOne(){return middle;}
//以下两个可以看作是成员变量
private T middle;
public T length; //length要用public或者默认访问修饰符
}
含有泛型方法getMiddle()的PairTest1普通类:
(第一种)
public class PairTest1
{
public static void main(String[] args)
{
String[] names = { "John", "Q.", "public" };
Pair<String> middle = ArrayAlg.<String>getMiddle(names); //如前所述Pair<String> middle = ArrayAlg.getMiddle(names);也可以。
System.out.println("mid = " + middle.getMiddleOne());
}
}
class ArrayAlg
{
public static <T> Pair<String> getMiddle(T[] b) //这里的泛型方法返回类型为Pair<String>
{
return new Pair<String>((String)b[b.length / 2]); //b[b.length / 2]是T[]类型,所以要强制类型转换。
}
}
或者
(第二种)
public class PairTest1
{
public static void main(String[] args)
{
String[] names = { "John", "Q.", "public" };
Pair<String> middle = ArrayAlg.<String>getMiddle(names); //如前所述Pair<String> middle = ArrayAlg.getMiddle(names);也可以。
System.out.println("mid = " + middle.getMiddleOne());
}
}
class ArrayAlg
{
public static <T> Pair<T> getMiddle(T[] b) //这里的泛型方法返回类型为Pair<String>
{
return new Pair<T>(b[b.length / 2]); //b[b.length / 2]是T[]类型,所以无需强制类型转换。
}
}