文档

Java™ 教程-Java Tutorials 中文版
将旧版代码转换为使用泛型
Trail: Bonus
Lesson: Generics

将旧版代码转换为使用泛型

之前,我们展示了新旧代码如何互操作。现在,是时候看看“泛化”旧代码的难题了。

如果你决定将旧代码转换为使用泛型,则需要仔细考虑如何修改 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 的实例时,它才会成功,但是:

addAll() 的情况下,我们应该能够添加由 E 子类型的实例组成的任何集合。我们在 Generic Methods 部分中看到了如何正确处理这种情况。

你还需要确保修订后的 API 保留与旧客户端的二进制兼容性。这意味着 API 的擦除必须与原始的,未经过授权的 API 相同。在大多数情况下,这自然会失败,但有一些微妙的情况。我们将检查我们遇到的最细微的案例之一,方法 Collections.max()。正如我们在 More Fun with Wildcards 中所看到的,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 为类型形参提供 multiple bounds (多个边界) 的示例。已知具有多个边界的类型变量是边界中列出的所有类型的子类型。当使用多个边界时,边界中提到的第一种类型用作类型变量的擦除。

最后,我们应该记得 max 仅从其输入集合中读取,因此适用于 T 的任何子类型的集合。

这将我们带到 JDK 中使用的实际签名:

public static <T extends Object & Comparable<? super T>> 
        T max(Collection<? extends T> coll)

在实践中出现任何如此复杂的事情是非常罕见的,但专家库设计师应该准备好在转换现有 API 时仔细考虑。

需要注意的另一个问题是 covariant returns (协变返回),即改进子类中方法的返回类型。你不应该在旧 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,否则它将无法正确覆盖 Barcreate() 方法。此外,必须修改 Baz,因为代码将被写入拒绝 - Baz 中的 create() 的返回类型是不是 Barcreate() 的返回类型的子类型。


Previous page: More Fun with Wildcards
Next page: Acknowledgements