面向对象的一个重要目标是对代码重用的支持。支持这个目标的一个重要的机制就是泛型机制(generic mechanism):如果除去对象的基本类型外,实现方法是相同的,纳闷我们就可以泛型实现(generic implementation)来描述这种基本的功能。例如,可以编写一个方法,将由一些项组成的数组排序;方法的逻辑关系与被排序的对象的类型无关,此时可以使用泛型方法。
(本文所有代码均在git)
1. 利用Java5泛型特性实现泛型构件
public class Generic<AnyType> { //指出Generic有一个类型参数
private AnyType storedValue;
public AnyType read(){
return storedValue;
}
public void write(AnyType x){
storedValue = x;
}
}
第一行指出,Generic有一个类型参数,在Generic内部,我们可以声明泛型类型的域和使用泛型类型作为参数或返回类型的方法。例如在类Generic<String>的write方法需要一个String类型的参数。如果传递其他参数那将产生一个编译错误。
也可以声明接口是泛型的。在Java5中,Comparable接口是泛型的,通过使类变成泛型类,以前只有在运行时才能报告的很多错误如今变成了编译时的错误。
public interface Comparable<AnyType>{
public int compareTo(AnyType other);
}
2. 自动装箱/拆箱
如果一个int型被传递到需要一个Integer对象的地方,那么编译器将在幕后插入一个对Integer方法构造方法的调用,这就叫做自动装箱。而如果一个Integer对象被放到需要int型量的地方,则编译器将在幕后插入一个对intValue方法的调用,这就叫做自动拆箱。
public class BoxingDemo { //自动装箱和自动拆箱
public static void main(String[] args) {
Generic<Integer> g = new Generic<Integer>();
g.write(37);
int val = g.read();
System.out.println("值为:" + val);
}
}
注意:
在Generic中引用的那些实体仍然是Integer对象;在Generic实例化中,int不能够代替Integer。
3. 菱形运算符
在第2点的代码中,第4行有些烦人,因为既然g是Generic<Integer>类型的,显然创建的对象也必须是Generic<Integer>类型的,任何其他类型的参数都会产生编译错误。Java7中增加了一种新的语言特性,称为菱形运算符,使得第5行可以改写为
Generic<Integer> g = new Generic<>();
菱形运算符在不增加开发者负担的情况下简化了代码。 以下为带菱形运算符的Java7代码。
public class BoxingDemo { //自动装箱和自动拆箱
public static void main(String[] args) {
Generic<String> g = new Generic<>();
g.write("aas");
String val = g.read();
System.out.println("值为:" + val);
}
}
4. 带有限制的通配符
public static double totalArea(Collection<Shape> arr){
double total = 0;
for(Shape s:arr){
if(s != null){
total += s.area();
}
}
return total;
}
上述代码中,如果传入的参数是Collection<Shape>,程序会正常运行。但是想要传入的参数是Collection<Square>时(Square extends Shape),会发现不能传入。
The method totalArea(Collection<Shape>) in the type Generic<String> is not applicable for the arguments (Collection<Square>)
Java5中用通配符(wildcard)来弥补这个不足。通配符用来表示参数类型的子类(或超类)。下面代码中totalArea的参数为Collection<T>,其中T is-A Shape。因此,Collection<Shape>和Collection<Square>都是可以接受的参数。通配符中还可以不带限制使用(extends Object),或不用extends而用super(来表示超类而不是子类)等。
//通配符修正后的方法
public static double totalArea(Collection<? extends Shape> arr){
double total = 0;
for(Shape s:arr){
if(s != null){
total += s.area();
}
}
return total;
}
5. 泛型static方法
从某种意义上说,通配符修正后的totalArea方法是泛型方法,因为它能够接受不同类型的参数。但是这里没有特定类型的参数表,正如Generic类的声明中所作的那样。
有时候特定类型很重要,原因如下:
1.该特定类型用来做返回类型;
2.该类型用在多于一个的参数类型中;
3.该类型用于声明一个局部变量。
声明一种带有若干类型参数的显式泛型方法:
//显式泛型方法
public static <AnyType> boolean contains(AnyType[] arr, AnyType x){
for(AnyType val:arr){
if(x.equals(val)){
return true;
}
}
return false;
}
6. 类型限界
在一些方法调用中,编译器需要证明其调用是合法的,如类AnyType调用compareTo方法需满足实现Comparable接口的“条件”。此时我们可以使用类型限界(type bound)解决这个问题。类型限界在尖括号内指定,它指定参数类型必须具有的性质。
//类型限界
public static <AnyType extends Comparable<? super AnyType>>
AnyType findMax(AnyType[] arr){
int maxIndex = 0;
for(int i = 1; i < arr.length; i++){
if(arr[i].compareTo(arr[maxIndex]) > 0){
maxIndex = i;
}
}
return arr[maxIndex];
}
上述代码中,AnyType is-A Comparable<T>,其中,T是AnyType的父类。由于我们不需要知道准确的类型T,因此可以使用通配符。
7. 函数对象
在第6点使用类型限界的findMin方法中有一个很重要的局限:它只对实现Comparable接口的对象有效,因为它使用compareTo作为所有比较决策的基础。
此时我们需要重写findMin,使它接受两个参数:一个是对象的数组,另一个是比较函数,该函数解释如何决定两个对象中哪个大哪个小。
一种将函数作为参数传递的独创方法是注意到对象既包含数据也包含方法,于是我们可以定义一个没有数据而只有一个类,并传递该类的一个实例。事实上,一个函数通过其放在一个对象内部而被传递。这样的对象通常叫作函数对象(function object)。
public class FunctionObject {
public static <AnyType> AnyType findMax(AnyType[] arr,
Comparator<? super AnyType> cmp){
int maxIndex = 0;
for(int i = 1; i < arr.length; i++){
if(cmp.compare(arr[i], arr[maxIndex]) > 0){
maxIndex = i;
}
}
return arr[maxIndex];
}
public static void main(String[] args) {
String[] arr = {"ZEBRA","alligator","crocodile"};
System.out.print(FunctionObject.findMax(arr, new CaseInsensitiveCompare()));
}
}
class CaseInsensitiveCompare implements Comparator<String>{
@Override
public int compare(String arg0, String arg1) {
return arg0.compareToIgnoreCase(arg1);
}
}
使用函数对象来实现排序升序或降序的例子:
/*数据比较接口*/
interface IntCompare{
public boolean cmp(int x,int y);
}
/*升序*/
class Cmp1 implements IntCompare{
public boolean cmp(int x,int y){
if(x > y){
return true;
}else{
return false;
}
}
}
/*降序*/
class Cmp2 implements IntCompare{
public boolean cmp(int x,int y){
if(x > y){
return false;
}else{
return true;
}
}
}
//选择排序实现
public class SelectSortX {
public static void SelectSort(int[] a, IntCompare cmp){
for(int i = 0; i < a.length; i++){
int min = i;
for(int j = i + 1; j < a.length; j++){
if(cmp.cmp(a[min], a[j])){
min = j;
}
if (i != min){
swap(a, i, min);
}
}
}
}
public static void swap(int[] a, int x, int y){
int temp = a[x];
a[x] = a[y];
a[y] = temp;
}
public static void main(String[] args) {
int[] array = {4,2,1,6,3,6,0,-5,1,1};
// SelectSort(array, new Cmp1()); //升序排序
SelectSort(array, new Cmp2()); //降序排序
for(int i = 0; i < array.length; i++){
System.out.printf("%d ",array[i]);
}
}
}
参考资料:
1. 《数据结构与算法分析》