一、泛型基础
泛型程序设计(Generic programming) 意味着编写的代码可以被很多不同类型的对象所重用。
示列:
/**
* 泛型类可以有多个类型变量如Pair<E,T>
*
* @param <T> 类型变量
*/
public class Pair<T> {
/** T可以代表任意类型 */
private T t;
Pair(T t) {
this.t=t;
}
/**
* 不带返回值的泛型方法,可以定义在普通类中(不是泛型类的类中)
*
* @param e 类型变量
* @param <E> 声明是一个泛型方法
*/
public <E> void method0(E e) {
}
public <E> String method1(E e) {
return "";
}
public <E> E method3(E e) {
return e;
}
public <E> E[] method4(E[] e) {
return e;
}
/*-------------------------------------*/
}
二、类型变量的限定
有时,类或方法需要对类型变量加以约束。如下例子,计算数组中的最小元素:
public class ArrayAlg {
public static <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;
}
}
但是,这里有一个问题。请看一下min 方法的代码内部。变量smallest 类型为T, 这意味着它可以是任何一个类的对象。怎么才能确信T 所属的类有compareTo 方法呢?
解决这个问题的方案是将T 限制为实现了Comparable 接口(只含一个方法compareTo 的
标准接口)的类。可以通过对类型变量T 设置限定(bound) 实现这一点:
public static <T extends Comparable> T min(T[] a). . .
读者或许会感到奇怪—在此为什么使用关键字extends 而不是implements ? 毕竟,Comparable 是一个接口。下面的记法
<T extends BoundingType〉
表示T 应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键字extends 的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字(如sub)。一个类型变量或通配符可以有多个限定, 例如:T extends Comparable & Serializable限定类型用“ &” 分隔,而逗号用来分隔类型变量。在Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。
三、泛型代码和虚拟机
虚拟机没有泛型类型对象—所有对象都属于普通类。在泛型实现的早期版本中, 甚至
能够将使用泛型的程序编译为在1.0 虚拟机上运行的类文件! 这个向后兼容性在Java 泛型开
发的后期被放弃了。
3.1类型擦除
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变名, 并替换为限定类型(无限定的变量用Object)。
例如,Pair的原始类型如下:
public class Pair {
private Object t;
Pair(Object t) {
this.t=t;
}
。。。。。
}
因为T 是一个无限定的变量,所以直接用Object替换。
结果是一个普通的类, 就好像泛型引人Java 语言之前已经实现的那样。
在程序中可以包含不N类型的Pair, 例如, Pair 或Pair。而擦除类
型后就变成原始的Pair 类型了。
原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用Object 替换。例如,类Pair 中的类型变量没有显式的限定,因此,原始类型用Object替换T。假定声明了一个不同的类型。
public class Interval
<T extends Comparable & Serializable>
implements Serializable {
private T lower;
private T upper;
public Interval (T first , T second) {
if (first .compareTo(second) <= 0) {
lower = first ; upper = second;
}
else {
lower = second; upper = first;
}
}
}
原始类型Interval 如下所示:
public class Interval implements Serializable {
private Comparable lower;
private Comparable upper;
public Interval (Comparable first, Comparable second) { . . . }
}
或许大家可能想要知道切换限定: <T extends Comparable & Serializable> 会发生什么。如果这样做 原始类型用Serializable 替换T, 而编译器在必要时要向Comparable插入强制类型转换。为了提高效率,应该将标签( tagging) 接口(即没有方法的接口)放在边界列表的末尾。
四、约束与局限性
4.1不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此, 没有Pair, 只有Pair。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double的值。
4.2运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此, 所有的类型查询只产生原始类型。例如:
if (a instanceof Pair) // Error
实际上仅仅测试a 是否是任意类型的一个Pair。下面的测试同样如此:
if (a instanceof Pair) // Error
或强制类型转换:
Pair<St「ing> p = (Pair) a; // Warning-can only test that a is a Pair
为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时, 倘若使用instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
同样的道理, getClass 方法总是返回原始类型。例如:
Pair stringPair = . .
Pai「< Employee〉employeePair = . .
if (stringPair.getClassO == employeePair .getClassO) // they are equal
其比较的结果是true, 这是因为两次调用getClass 都将返回Pair.class。
4.3不能创建参数化类型的数组
不能实例化参数化类型的数组, 例如:
Pair[] table = new Pair[10] ; // Error
这有什么问题呢? 擦除之后, table 的类型是Pair[]。 可以把它转换为Object[] :
Object[] objarray = table;
数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个Array-StoreException 异常:
obj array[0] = “Hello”; // Error component type is Pair
不过对于泛型类型, 擦除会使这种机制无效。以下赋值:
obj array[0] = new Pair< Employee>();
能够通过数组存储检査, 不过仍会导致一个类型错误。出于这个原因, 不允许创建参数
化类型的数组。
需要说明的是, 只是不允许创建这些数组, 而声明类型为Pair[] 的变量仍是合法
的。不过不能用new Pair[10] 初始化这个变量。
五、通配符类型
5.1<? extends BindingClass>
这个通配符限制为BindingClass的所有子类。可以使用返回值,但不能为方法提供参数。
? extends Employee get()
void set(? extends BindingClass)这样将不可能调用set方法。编译器只知道需要某个BindingClass的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配。使用get就不存在这个问题: 将get的返回值赋给一个BindingClass的引用完全合法。
这就是引人有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全的更改器方法了。
5.2<? super BindingClass>
这个通配符限制为BindingClass的所有超类型。可以为方法提供参数,但不能使用返回值。
void set(? super BindingClass)
? super Manager getFirst()这不是真正的Java语法,但是可以看出编译器知道什么。编译器无法知道set方法的具体类型,因此调用这个方法时不能接受类型为Object的参数。只能传递BindingClass类型的对象,或者某个子类型对象。另外,如果调用get, 不能保证返回对象的类型。只能把它赋给一个Object。
直观地讲,带有超类型限定的通配符可以向泛型对象写人,带有子类型限定的通配符可
以从泛型对象读取。
? extends xx
//表示上界类型通配符,我虽然不知道这个类型具体是什么,但他是xx和xx的子类
? super xx
//表示下界类型通配符,我虽然不知道这个类型具体是什么,但他是xx和xx的父类