文档

Java™教程
隐藏目录
将遗留代码转换为使用泛型
路径: 奖励
教训: 泛型

将旧代码转换为使用泛型

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

如果你决定将旧代码转换为使用泛型,你需要仔细考虑如何修改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的子类型的实例组成的集合。我们在泛型方法中已经看到了如何正确处理这种情况。

你还需要确保修订后的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 {
    // 工厂。应该创建它所声明的类的实例。
    public Foo create() {
        ...
    }
}

public class Bar extends Foo {
    // 实际创建一个 Bar。
    public Foo create() {
        ...
    }
}

利用协变返回,你将其修改为:

public class Foo {
    // 工厂。应该创建它所声明的类的实例。
    public Foo create() {
        ...
    }
}

public class Bar extends Foo {
    // 实际创建一个 Bar。
    public Bar create() {
        ...
    }
}

现在,假设你的代码的第三方客户端编写了以下内容:

public class Baz extends Bar {
    // 实际创建一个 Baz。
    public Foo create() {
        ...
    }
}

Java虚拟机不直接支持具有不同返回类型的方法覆盖。这个特性由编译器支持。因此,除非重新编译类 Baz,否则它不会正确地覆盖 Bar 中的 create() 方法。此外,Baz 必须被修改,因为代码将被拒绝,写在 Baz 中的 create() 的返回类型不是 Barcreate() 的返回类型的子类型。


上一页:更多通配符的乐趣
下一页:致谢