1 泛型程序设计
泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。
在 JAVA 增加泛型之前,泛型是用继承来实现的。
ArrayList 类只维护一个 Object 引用的数组:
public class Test{public static void main(String[] args) {ArrayList list = new ArrayList();list.add("Kaven");list.add(1); String str = (String)list.get(0);}
}
这种方法实现泛型存在两个问题:
- 获取一个元素时必须进行强制类型转换(当然除了 Object)。
- 没有错误检查,可以向 ArrayList 中添加任何类型的元素。
泛型程序设计提供了一个更好的解决方案:类型参数(Type Parameters)。
ArrayList 类有一个类型参数用来指示元素的类型:
public class Test{public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();list.add("Kaven1");list.add("kaven2"); String str = list.get(0);}
}
这使得代码具有更好的可读性,人们一看就知道这个 ArrayList 中包含的是 String 对象。
在 JAVA SE7 及以后的版本中,构造方法中可以省略泛型类型,省略的类型可以从变量的类型推断得出。
编译器也可以很好地利用这个信息,当调用 get 方法时,不需要进行强制类型转换,编译器就知道返回值类型为 String,而不是Object。
编译器还知道 ArrayList< String > 中 add 方法有一个类型为 String 的参数,这将比使用 Object 类型的参数安全一些,编译器可以进行检查,避免插入一个错误类型的对象。
2 定义简单泛型类
一个泛型类(Generic Class)就是具有一个或多个类型变量的类。
如下:
public class Test<T>{private T first;private T second;public Test(){first = null;second = null;}public Test(T first,T second){this.first = first;this.second = second;}public T getFirst(){return first;}public T getSecond(){return second;}
}
类型变量通常使用大写形式,且比较短。
在 JAVA 库中,使用变量 E 表示集合的元素类型,K 和 V 分别表示表的关键字与值的类型,T (需要时还可以用临近的字母 U 和 S )表示 “任意类型”。
3 泛型方法
实际上,还可以定义一个带有类型参数的简单方法:
public class Test{public static <T> T get(T t){return t;}
}
泛型方法可以定义在普通类中,也可以定义在泛型类中。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
public class Test{public static <T> T get(T t){return t;}public static void main(String[] args) {String str = Test.<String>get("kaven");}
}
在这种情况下,方法调用中可以省略 < String > 类型参数,编译器有足够的信息能够推断出所调用的方法。
偶尔也会出错:
public class Test{public static <T> T get(T... t){return t[t.length/2];}public static void main(String[] args) {//double str = Test.get(1.5 , 1 , 2 ); error}
}
错误信息:
类型不匹配:不能从 Number&Comparable< ? > 转换为 double
简单地说,编译器将会自动打包参数为 1 个 Double 和 2 个 Integer 对象,而后寻找这些类的共同超类型,事实上,找到 2 个这样的超类型 Number 和 Comparable接口,其本身也是一个泛型类型。
在这种情况下,可以采取的补救措施是将所有的参数写为 double 值。
4 类型变量的限定
有时,类或方法需要对类型变量加以约束。
下面是一个典型的例子:
public class Test{public static <T extends Comparable<T>> T min(T[] a){if(a==null || a.length==0) return null;T smallest = a[0];for(int i = 1;i < a.length; i++) if(smallest.compareTo(a[i]) > 0) smallest = a[i];return smallest;}
}
根据上面程序可以看出,我们要计算数组中的最小元素,所以类型变量 T 之间必须可以比较大小,为了解决这个问题,可以让类型变量 T extends Comparable< T >。
所以可以通过对类型变量 T 设置一些限定来符合一些要求。
现在,泛型的 min 方法只能被实现了 Comparable 接口的类的数组调用。
< T extends BoundType>
上面表示 T 应该是限定类型的子类型,T 和限定类型可以是类,也可以是接口。
选择关键字 extends 的原因是更接近子类的概念。
< T extends BoundType1 & BoundType2 ,U extends BoundType3>
限定类型用 “&” 分隔,而逗号用来分隔类型变量。
在 JAVA 的继承中,一个类可以根据需要拥有多个接口超类型,但是只能继承一个父类,在这里也一样,限定类型中至多有一个类,可以有多个接口,如果限定类型中有类,那么它必须是限定列表的第一个。