泛型程序设计(Generic parogramming)意味着编写的代码可以被很多不同类型的对象所重用。
泛型,提供了类型参数(type parameters),使得程序具有更好的可读性和安全性。
例如:
ArrayList<String> files = new ArrayList<String>();
表明了ArrayList类有一个类型参数String来指示元素的类型。
泛型类
泛型类的简单实例:
Pair class Pair<T, U>{......}
泛型类的定义:一个泛型类就是具有一个或多个类型变量的类。如上述泛型类Pair具有两个类型变量(T和U)。
类型变量:用大写的T(或U,S)表示普通类型变量,E表示集合的元素类型,K和V分别表示表的关键字与值的类型。
实例化泛型类型:用具体的类型替换类型变量就可以实例化泛型类型。
泛型方法
泛型方法可以定义在普通类中,也可以定义在泛型类中。
例如:
public static <T> T getMiddle(T[] a){
return a[a.length / 2];
}
类型变量<T>,要放在修饰符的后面,返回类型的前面。
调用泛型方法,在方法名前的尖括号中放入具体的类型。
String[] names = {"john", "Bob", "Lily"};
String middle = ArrayAlg.<String>getMiddle(names);
但是,大多数情况在,我们可以省略类型参数,直接调用方法。
String[] names = {"john", "Bob", "Lily"};
String middle = ArrayAlg.getMiddle(names);
类型变量的限定
有时候,我们必须要求某个类型变量必须实现某个接口再能让程序正确执行,比如 public static <T> T min(T[] a){ ... }方法,很容易知道T必须是实现了Comparale接口才能得到最小值。我们只需要:
public static <T extends Comparable> T min(T[] a){ ... }
这表示,min方法只能被实现了Comparable接口的类的数组调用。
<T extends BoundingType>,T是绑定类型(BoundingType)的子类型,BoundingType可以是类或者接口。当BoundingType有多个时,用&分隔,且限定中至多有一个类,如果有类,则类必须是限定列表中的第一个。
泛型代码和虚拟机
虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义一个泛型类型,都自动提供一个相应的原始类型(raw type)。
原始类型用第一个限定的类型变量来替换,如果没有限定就用Object来替换。
翻译泛型表达式:
当调用泛型方法是,如果擦除返回类型,编译器会插入强制类型转换。
例如:
Pair<Employee> buddies = ......
Employee buddy = buddies.getFirst();
getFirst方法在泛型中返回一个Employee类型的对象,但是类型擦除后,返回一个Object类型的对象,这时,编译器会自动插入(Employee)buddies.getFirst(),强制类型转换。
翻译泛型方法:
public static <T extends Comparable> T min(T[] a) ---> public static Comparable min(Comparable[] a)
合成的桥方法:
这是为了解决类型擦除导致的多态性丧失而创造的方法。
小例子:
擦除类型前:
泛型类Pair:
public class Pair<T>{
........
public void setSecond(T second){......}
}
一个普通的类:
class DateInterval extends Pair<Date>{
public void setSecond(Date second){...}
}
擦除类型后
Pair类变成了:
public class Pair{
........
public void setSecond(Object second){......}
}
这个DateInterval类变成了:
class DateInterval extends Pair{
public void setSecond(Date second){...};
}
可以看出,两个setSecond方法的参数完全不同,他们是两个完全不同的方法,如果运行如下语句:
DateInterval interval = new DateInterval(...);
Pair<Date> pair = interval;
pair.setSecond(aDate);
那么,最后一句,pair.setSecond(aDate)是调用的那个方法呢?下面来分析一下:
setSecond(Date second)方法仅在DateInterval中有效,setSecond(Objiec second)在Pair及其子类中都有效。这里已经擦除了类型,所以pair.setSecond(aDate)中的pair为Pair类的对象,必将调用Pair类中的那个setSecond(Object second)方法。这样,setSecond本应具有的多态性消失了。注意:DateInterval类并没有重写setSecond方法。
所以,要利用合成的桥方法重新找回多态性。
合成的桥方法只需要在DateInterval类中加一个setSecond(Object second)方法:
class DateInterval extends Pair{
public void setSecond(Date second){...};
public void setSecond(Object second){
setSecond((Date)second);
}
}
就这样,setSecond(Object second)重写了超类中的同签名方法,实现了多态性。
约束与局限性
- 约束与局限性大多是由类型擦除引起的。
- 不能用基本类型实例化类型参数。由于primitive type不是Object的子类。
- 运行时类型查询只适用于原始类型(raw type)。a instanceof Pair<T>与a instanceof Pair<String>有相同结果,T被忽略了。
- 不能抛出也不能捕获泛型类实例。
- 参数化类型的数组不合法。Pair<String>[] myTest = new Pair<String>[10]; //错误
- 不能实例化类型变量。new T(...);//错误
- 泛型类的静态上下文中类型变量无效。
- 注意擦除后的冲突。
通配符(wildcard type)
Pair<? extends Employee>子类型限定的通配符
表示任何泛型Pair类型,它的类型参数是Employee的子类。
Pair<? extends Employee>是Pair<Employee>和Pair<Manager>的超类,而Pair<Employee>和Pair<Manager>没有任何关系。
- ? extends Employee getFirst();
- void setFirst(? extends Employee);
Pair<? extends Employee>中某个访问器getFirst()方法的返回类型是? extends Employee,它赋给一个Employee类型的对象是完全合法的。而setFirst方法,编译器只知道参数类型是Employee类或其子类,具体不知道,所以编译器拒绝传递任何特定的类型。
Pair<? super Employee>超类型限定的通配符
表示任何泛型Pair类型,它的类型参数是Employee的超类。
Pair<? super Employee>是Pair<Employee>和Pair<Object>的超类。
- ? super Employee getFirst();
- void setFirst(? super Employee);
- ? getFirst();
- void setFirst(? );