将旧版代码转换为使用泛型
原文: https://docs.oracle.com/javase/tutorial/extra/generics/convert.html
之前,我们展示了新旧代码如何互操作。现在,是时候看看“泛化”旧代码的难题了。
如果您决定将旧代码转换为使用泛型,则需要仔细考虑如何修改 API。
您需要确保通用 API 不会过度限制;它必须继续支持 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 的原始合同。 containsAll()
方法适用于任何类型的传入集合。只有当传入的集合实际上只包含E
的实例时,它才会成功,但是:
- 传入集合的静态类型可能不同,可能是因为调用者不知道传入的集合的精确类型,或者可能是因为它是
Collection<S>
,其中S
是E
的子类型。 - 使用不同类型的集合调用
containsAll()
是完全合法的。例程应该有效,返回false
。
在addAll()
的情况下,我们应该能够添加由E
子类型的实例组成的任何集合。我们在通用方法一节中看到了如何正确处理这种情况。
您还需要确保修订后的 API 保留与旧客户端的二进制兼容性。这意味着 API 的擦除必须与原始的,未经过授权的 API 相同。在大多数情况下,这自然会失败,但有一些微妙的情况。我们将研究我们遇到的最细微的案例之一,方法Collections.max()
。正如我们在章节中看到的更多有趣的通配符,max()
的合理签名是:
public static <T extends Comparable<? super T>>
T max(Collection<T> coll)
这很好,除了这个签名的删除是:
public static Comparable max(Collection coll)
这与max()
的原始签名不同:
public static Object max(Collection coll)
当然可以为max()
指定这个签名,但是没有完成,所有调用Collections.max()
的旧二进制类文件都依赖于返回Object
的签名。
我们可以通过在形式类型参数T
的边界中显式指定超类来强制擦除不同。
public static <T extends Object & Comparable<? super T>>
T max(Collection<T> coll)
这是使用语法T1 & T2 ... & Tn
为类型参数赋予多个边界的示例。已知具有多个边界的类型变量是绑定中列出的所有类型的子类型。当使用多边界时,边界中提到的第一种类型用作类型变量的擦除。
最后,我们应该记得max
仅从其输入集合中读取,因此适用于T
的任何子类型的集合。
这将我们带到 JDK 中使用的实际签名:
public static <T extends Object & Comparable<? super T>>
T max(Collection<? extends T> coll)
在实践中出现任何如此复杂的事情是非常罕见的,但专家图书馆设计师应该准备好在转换现有 API 时仔细考虑。
需要注意的另一个问题是协变返回,即改进子类中方法的返回类型。您不应该在旧 API 中利用此功能。为了了解原因,让我们看一个例子。
假设您的原始 API 具有以下形式:
public class Foo {
// Factory. Should create an instance of
// whatever class it is declared in.
public Foo create() {
...
}
}
public class Bar extends Foo {
// Actually creates a Bar.
public Foo create() {
...
}
}
利用协变返回,您可以将其修改为:
public class Foo {
// Factory. Should create an instance of
// whatever class it is declared in.
public Foo create() {
...
}
}
public class Bar extends Foo {
// Actually creates a Bar.
public Bar create() {
...
}
}
现在,假设您的代码的第三方客户端编写了以下内容:
public class Baz extends Bar {
// Actually creates a Baz.
public Foo create() {
...
}
}
Java 虚拟机不直接支持覆盖具有不同返回类型的方法。编译器支持此功能。因此,除非重新编译类Baz
,否则它将无法正确覆盖Bar
的create()
方法。此外,Baz
必须被修改,因为代码将被写入拒绝 - Baz
中create()
的返回类型不是Bar
中create()
的返回类型的子类型。