分类: Java
2007-05-20 11:23:40
Java1.5泛型指南中文版(Java1.5 Generic Tutorial)
英文版pdf下载链接:
译者:
目 录
(第七部分)
前面,我们讲述了新老代码如何交互。现在,是时候研究更难的泛型化老代码的问题了。
如果你决定把老代码转换成使用泛型的代码,你需要仔细考虑怎么修改你的API。
你必须确定泛型化的API不会过分严格,它必须继续支持原来的API调用契约(original contract of the API)。在考虑几个 java.util.Collection中的例子。泛型代码之前的API像:
interface Collection {
public boolean containsAll(Collection c);
public boolean addAll(Collection c);
}
一个稚嫩的泛型化尝试:
interface Collection<E> {
public boolean containsAll(Collection<E> c);
public boolean addAll(Collection<E> c);
}
这当然是类型安全的,但是它不支持这个API的原始契约(original contract)。
containsAll() 方法能对所有进来的任意类型的collection工作。它只有在传进来的collection中真正只包含E的实例才成功,但是:
l 传进来的collection的静态类型可能不同,可能是因为调用者不知道传进来的colleciton的精确类型,或者因为它是一个Collection,S是E的子类型。
l 用一个不同类型的collection来调用containsAll()应该是合法的。这个例程应该能够工作,返回false。
对addAll(),我们应该能够添加任何元素是E的子类型的collection。我们已经在第5部分讲述了怎么正确的处理这种情况。
你还应该保证修订过的API保持与老客户端的二进制兼容。者以为者API的erasure必须与老的未泛型化版本一样。在大多数情况下,这是很自然的结果,但是有些精巧的情形(subtle cases)。我们看看我们已经碰到过的精巧的情形中的一个(one of the subtle cases),方法Collections.max()。就像我们在第9部分看到的,一个似是而非的max()的方法签名是:
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
这很好,除了擦除(erasure)后的签名是:
public static Comparable max(Collection coll)
这和老版本的max() 的签名不同:
public static Object max(Collection coll)
当然可以把max()定义为这个签名,但是这没有成为现实,因为所有调用了Collections.max()的老的二进制class文件依赖于返回Object的签名。
我们可以强迫the erasure不同,通过给形式类型参数T显式的定义一个父类。
public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll)
这是一个对一个类型参数给定多个界限(multiple bounds)的例子,是用语法 T1 & T2 … & Tn。一个有多个界限的类型的参数是所有界限中列出来的类型的子类。当多个界限被使用的时候,界限中的第一个类型被用作这个类型参数的erasure。
(原文:This is an example of giving multiple bounds for a type parameter, using the syntax T1& T2 ... &
最后,我们应该想到max只从传进来的collection中读取数据,因此它对元素是T的子类的collection可用。这给我们JDK中使用的真正的签名:
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
实际中出现那么棘手的问题是很罕见的,但是专业库设计师应该准备好非常仔细的考虑转换现存的API。
另一个需要小心的问题是协变式返回值(covariant returns),就是说在子类中获得一个方法的返回值(refining the return type of a method in a subclass)。在老API中你无法使用这个特性带来的好处。
为了知其原因,让我们看一个例子。
假定你的原来的API是下面的形式:
public class Foo {
public Foo create(){...}
// Factory, should create an instance of whatever class it is declared in
}
public class Bar extends Foo {
public Foo create(){...} // actually creates a Bar
}
为了使用协变式返回值的好处,你把它改成:
public class Foo {
public Foo create(){...}
// Factory, should create an instance of whatever class it is declared in
}
public class Bar extends Foo {
public Bar create(){...} // actually creates a Bar
}
现在,假定你的一个第三方客户代码:
public class Baz extends Bar {
public Foo create(){...} // actually creates a Baz
}
Java虚拟机并不直接支持不同类型返回值的方法重载。这个特性是由编译器来支持的。因此,除非Baz类被重新编译,它不会正确的重载Bar的create()方法,而且,Baz必须被修改,因为Baz的代码被拒绝,它的create的返回值不是Bar中create返回值的子类。(原文: Consequently, unless the class Baz is recompiled, it will not properly override the create() method of Bar.Furthermore, Baz will have to be modified, since the code will be rejected as written - the return type of create() in Baz is not a subtype of the return type of create() in Bar.)
(译注:上面的一段话有些莫名其妙,我测试过这个例子,在jdk1.4下,三个类都编译之后改变Bar,只在jdk5下重新编译Bar,然后在jdk5下,Baz仍然能够被使用,当然那,无法使用 Baz b = baz.create();这样的代码。)
Erik Ernst, Christian Plesner Hansen, Jeff Norton, Mads Torgersen, Peter von der Ah´e and Philip Wadler contributed material to this tutorial.
Thanks to David Biesack, Bruce Chapman, David Flanagan, Neal Gafter, ¨ Orjan Petersson,Scott Seligman, Yoshiki Shibata and Kresten Krab Thorup for valuable feedback on earlier versions of this tutorial. Apologies to anyone whom I’ve forgotten.