[译] 泛型 10.将老式代码转换为使用泛型

<< 通配符的更多用法 · 目录 · 致谢 >>

                                                                                  

将老式代码转换为使用泛型 (Converting Legacy Code to Use 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的实例时,它才会成功,但是:

  • 传入集合的静态类型可能不同,可能是因为调用方不知道传入的集合的确切类型,或者是因为它是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)

这是一个为类型参数提供多界(multiple bounds)的例子,使用语法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()返回类型不是Bar中的create()返回类型的子类型。

                                                                                  

<< 通配符的更多用法 · 目录 · 致谢 >>

本文译自:https://docs.oracle.com/javase/tutorial/extra/generics/convert.html

转载于:https://my.oschina.net/tita/blog/2886710

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值