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

之前,我们展示了新代码和旧代码如何互操作。现在,该看一下“生成”旧代码的难题了。

如果您决定将旧代码转换为使用泛型,则需要仔细考虑如何修改 API。

您需要确保通用 API 不受过度限制;它必须 continue 支持 API 的原始 Contract。再次考虑java.util.Collection中的一些示例。通用 API 如下所示:

interface Collection {
    public boolean containsAll(Collection c);
    public boolean addAll(Collection c);
}

天真的try将其夸大如下:

interface Collection<E> {

    public boolean containsAll(Collection<E> c);
    public boolean addAll(Collection<E> c);
}

尽管这肯定是类型安全的,但它不符合 API 的原始 Contract。 containsAll()方法适用于任何类型的传入集合。仅当传入集合实际上仅包含E实例时,它才会成功,但是:

  • 传入集合的静态类型可能有所不同,可能是因为调用者不知道所传入集合的确切类型,或者可能是因为它是Collection<S>,其中SE的子类型。

  • 调用具有不同类型的集合的containsAll()是完全合法的。该例程应该工作,返回false

对于addAll(),我们应该能够添加任何包含E子类型实例的集合。我们在Generic Methods部分中了解了如何正确处理这种情况。

您还需要确保修订后的 API 保留与旧 Client 端的二进制兼容性。这意味着该 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仅从其 Importing 集合中读取,因此适用于T的任何子类型的集合。

这使我们了解了 JDK 中使用的实际签名:

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

涉及到的任何事情在实践中都是很少见的,但是在转换现有的 API 时,maven 库设计人员应该准备好仔细考虑。

需要注意的另一个问题是协变量返回,即在子类中优化方法的返回类型。您不应该在旧的 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() {
        ...
    }
}

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

public class Baz extends Bar {
    // Actually creates a Baz.
    public Foo create() {
        ...
    }
}

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