12.1 为什么要使用泛型程序设计
generic programming意味着编写的代码可以被很多不同类型的对象所重用,例如,我们并不希望为聚集String和File而编写不同的类,实际上也不需要这样做,因为一个ArrayList可以聚集任何类型的对象,这是一个泛型程序设计的实例
java SE 5.0之前 java泛型程序设计是用继承实现的 ArrayList只维护一个Obj引用的数组 这样的实现有两个问题
1当获取一个值时必须进行强制类型转换,此外这里没有检查错误,可以向数据列表中添加任何类的对象
ArrayList list
=
new ArrayList();
list. add( "lee");
System. out.println((String)list. get( 0));
list. add( new File( "..."));
list. add( "lee");
System. out.println((String)list. get( 0));
list. add( new File( "..."));
对于这个调用 编译和运行都不会出错,然而在其他地方,如果将get的结果强制类型转换为String类型 就会产生一个错误
泛型提供了一个更好的解决方案 :类型参数(type parameters),ArrayList类有一个类型参数用来指示元素的类型
ArrayList
<String
> list
=
new ArrayList
<String
>();
这使得代码具有更好的可读性,人们一看就知道这个数组列表中包含的是String对象 编译器也可以很好的利用这个信息,当调用get的时候,不需要进行强制类型转换,编译器就知道返回值类型为String而不是Obj。
编译器还知道list中add方法有一个类型为String的参数 这将比使用Obj类型的参数安全一些,现在,编译器可以进行检查,避免插入错误类型的对象
list.
add(
new File(
"..."));
这时是无法通过编译的,出现编译错误比类在运行时出现类的强制类型转转异常要好得多,类型参数的魅力在于,使得程序具有更好的可读性和安全性.
泛型程序设计划分为3个熟练级别,基本级别是,仅仅使用泛型类--典型的是像ArrayList这样的集合-不必考虑他们的工作方式与原因,大多数应用程序员将会停留在这一级别上,知道出现了什么问题(书中到此没有详细讲明哪3个级别)
12.2 简单泛型类的定义
一个泛型类(generic class)就是具有一个或多个类型变量的类
public
class Pair
<T
> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
Pair类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面,泛型类可以有多个类型变量,例如,可以定义pair类,其中第一个域和第二个域使用不同的类型;
public
class Pair
<T, U
>
类型变量使用大写形式,且比较短,这是很常见的,在java库中,使用变量
E表示集合元素的类型,
K和
V分别表示表的关键字的键值对(Key-Value)的类型,
T(需要时还可以用临近的字母U和S)表示“任意类型”
public
class PairTest1 {
public static void main(String[] args) {
String[] words = { "Mary", "had", " a", " little", " lamb"};
Pair <String > pair = PairAlg.minmax(words);
System.out.println( "min:" +pair.getFirst());
System.out.println( "max:" +pair.getSecond());
}
}
class PairAlg{
public static Pair <String > minmax(String[] arr){
if (arr == null || arr.length == 0) {
return null;
}
String max = arr[ 0];
String min = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
} if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair <String >(min, max);
}
}
public static void main(String[] args) {
String[] words = { "Mary", "had", " a", " little", " lamb"};
Pair <String > pair = PairAlg.minmax(words);
System.out.println( "min:" +pair.getFirst());
System.out.println( "max:" +pair.getSecond());
}
}
class PairAlg{
public static Pair <String > minmax(String[] arr){
if (arr == null || arr.length == 0) {
return null;
}
String max = arr[ 0];
String min = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
} if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair <String >(min, max);
}
}
12.3 泛型的方法
class PairAlg{
public static <T > T getMiddle(T[] arr){
return arr[arr.length / 2];
}
}
public static <T > T getMiddle(T[] arr){
return arr[arr.length / 2];
}
}
这个方法是在普通类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(public static)的后面,返回类型的前面.泛型方法可以定义在普通类中,也可以定义在泛型类中. 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:
String[] words
=
{
"Mary"
,
"had"
,
" a"
,
" little"
,
" lamb"
};
Integer[] nums
= {
2,
5,
6,
8,
9};
System.out.println(PairAlg. <Integer >getMiddle(nums)); //注意尖括号位置
System.out.println(PairAlg. <String >getMiddle(words)); //注意尖括号位置
System.out.println(PairAlg. <Integer >getMiddle(nums)); //注意尖括号位置
System.out.println(PairAlg. <String >getMiddle(words)); //注意尖括号位置
在大多数情况下,可以省略尖括号,因为编译器有足够的信息能推算出所调用的方法,它用names的类型与泛型类型T进行匹配并推断出T一定是该类型
12.4 类型变量的限定
有时,类或方法需要对类型变量加以约束,下面这个我们计算数据中的最小元素
public
static
<T
extends Comparable
> T min(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T smallest = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(smallest.compareTo(arr[i]) > 0){
smallest = arr[i];
}
}
return smallest;
}
if (arr == null || arr.length == 0) {
return null;
}
T smallest = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(smallest.compareTo(arr[i]) > 0){
smallest = arr[i];
}
}
return smallest;
}
public static <T extends Comparable>
实际上Comparable接口本身就是一个泛型类型,目前,我们忽略其复杂性以及编译器产生的警告,在12.8节有介绍怎么适当地使用类型参数,或许大家会感到奇怪,在此为什么使用关键字extends,Comparable不是一个接口吗?
<T extends Bounding Type>
表示T应该是绑定类型的子类型(subtype),T和绑定类型可以是类,也可以是接口,选择关键字extends的原因是更接近子类的概念,并且JAVA的设计者也不打算在语言中再添加一个新的关键字
一个类型变量或通配符可以有多个限定,例如
<T extends Comparable & Serializable>
public
class PairTest2 {
public static void main(String[] args) {
GregorianCalendar[] birthdays = {
new GregorianCalendar( 1988, Calendar.JUNE, 15),
new GregorianCalendar( 1970, Calendar.APRIL, 6),
new GregorianCalendar( 1964, Calendar.FEBRUARY, 31),
new GregorianCalendar( 1990, Calendar.NOVEMBER, 23)
};
Pair <GregorianCalendar > gregorianCalendar = minmax(birthdays);
System.out.println( "min:" +gregorianCalendar.getFirst());
System.out.println( "max:" +gregorianCalendar.getSecond());
}
public static <T extends Comparable & Serializable > Pair <T > minmax(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T max = arr[ 0];
T min = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
} if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair <T >(min, max);
}
}
public static void main(String[] args) {
GregorianCalendar[] birthdays = {
new GregorianCalendar( 1988, Calendar.JUNE, 15),
new GregorianCalendar( 1970, Calendar.APRIL, 6),
new GregorianCalendar( 1964, Calendar.FEBRUARY, 31),
new GregorianCalendar( 1990, Calendar.NOVEMBER, 23)
};
Pair <GregorianCalendar > gregorianCalendar = minmax(birthdays);
System.out.println( "min:" +gregorianCalendar.getFirst());
System.out.println( "max:" +gregorianCalendar.getSecond());
}
public static <T extends Comparable & Serializable > Pair <T > minmax(T[] arr){
if (arr == null || arr.length == 0) {
return null;
}
T max = arr[ 0];
T min = arr[ 0];
for( int i = 0; i <arr.length; i ++){
if(max.compareTo(arr[i]) < 0){
max = arr[i];
} if(min.compareTo(arr[i]) > 0){
min = arr[i];
}
}
return new Pair <T >(min, max);
}
}
12.5 泛型代码和虚拟机
虚拟机没有泛型类型对象-所有对象都属于普通类,在泛型实现的早期版本中,甚至能够将使用泛型的程序编译为1.0虚拟机上运行的类文件
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名.
擦除(erased)类型变量,并替换为限定类型(无限定的变量用Obj)
public
class Pair
<T
> {
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
public Pair() {
super();
}
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(T second) {
this.second = second;
}
private T first;
private T second;
}
上面的Pair<T>原始类型如下
public
class Pair {
public Pair() {
super();
}
public Pair(Object first, Object second) {
super();
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
private Object first;
private Object second;
}
public Pair() {
super();
}
public Pair(Object first, Object second) {
super();
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
public void setFirst(Object first) {
this.first = first;
}
public void setSecond(Object second) {
this.second = second;
}
private Object first;
private Object second;
}
因为T是一个无限定的类型,所以直接用Obj来进行替换 的出来的结果是一个普通的类,就像泛型引入java语言之前已经实现的那样
在程序中可以包含不同类型的Pair,例如Pair<String>,Pair<GregorianCalendar>,而擦除类型后就变成原始的Pair类型了
原始类型用第一个限定的类型变量来替换,如果没有给定限定就用Obj替换
12.5.1 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,例如,下面这个语句序列
Pair
<GregorianCalendar
> gregorianCalendar
= minmax(birthdays);
GregorianCalendar min = gregorianCalendar. getFirst();
GregorianCalendar min = gregorianCalendar. getFirst();
擦除getFirst()返回类型后将返回Obj类型,编译器自动插入GregorianCalendar的强制类型转换,也就是说,编译器吧这个方法调用翻译为两条虚拟机指令:
.对原始方法
Pair.getFirst()的调用
.将返回的Obj类型强制转换为
gregorianCalendar
当存取一个泛型域时也要插入强制类型转换,假设Pair类的fiirst域和second域都是公有的(也许这不是一种良好的编程习惯,但在java中是合法的).表达式:
GregorianCalendar min
= gregorianCalendar.
first();
也会在结果字节码中插入强制类型转换.
12.5.2 翻译泛型方法 (12.5.2 & 12.5.3有点理解不了 所以都没有详细做笔记 以后再深入研究)
类型擦除也会出现在泛型方法中,程序员通常认为下述的泛型方法
是一个完整的方法族,而擦除类型之后,只剩下一个方法
12.5.3 调用遗留代码
12.6 约束和局限性
在下面几节中,将阐述使用java泛型时需要考虑的一些限制,大多数限制都是由类型擦除引起的.
12.6.1 不能用基本类型实例化参数 没有Pair<double>只有Pair<Double>
12.6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特性的非泛型类型,因此,所有的类型查询只产生原始类型(
暂时跳过这一章,用的少后面不好理解,以后攻破)