Documentation

The Java™ Tutorials
Hide TOC
Converting Legacy Code to Use Generics将遗留代码转换为使用泛型
Trail: Bonus
Lesson: Generics

Converting Legacy Code to Use Generics将遗留代码转换为使用泛型

Earlier, we showed how new and legacy code can interoperate. 早些时候,我们展示了新代码和旧代码如何进行互操作。Now, it's time to look at the harder problem of "generifying" old code.现在,我们来看看“泛化”旧代码这一更难的问题。

If you decide to convert old code to use generics, you need to think carefully about how you modify the API.如果决定将旧代码转换为使用泛型,则需要仔细考虑如何修改API。

You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. 您需要确保通用API没有过度限制;它必须继续支持API的原始合同。Consider again some examples from java.util.Collection. 再考虑一些java.util.Collection的例子。The pre-generic API looks like:预通用API如下所示:

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

A naive attempt to generify it would be the following:将其泛化的天真尝试如下:

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

While this is certainly type safe, it doesn't live up to the API's original contract. 虽然这肯定是类型安全的,但它不符合API的原始约定。The containsAll() method works with any kind of incoming collection. containsAll()方法适用于任何类型的传入集合。It will only succeed if the incoming collection really contains only instances of E, but:只有当传入集合真正只包含E的实例时,它才会成功,但是:

In the case of addAll(), we should be able to add any collection that consists of instances of a subtype of E. addAll()的情况下,我们应该能够添加由E的子类型的实例组成的任何集合。We saw how to handle this situation correctly in section Generic Methods.我们在泛型方法一节中看到了如何正确处理这种情况。

You also need to ensure that the revised API retains binary compatibility with old clients. 您还需要确保修订后的API与旧客户端保持二进制兼容性。This implies that the erasure of the API must be the same as the original, ungenerified API. 这意味着对API的擦除必须与原始的未泛化API相同。In most cases, this falls out naturally, but there are some subtle cases. 在大多数情况下,这是自然而然的,但也有一些微妙的情况。We'll examine one of the subtlest cases we've encountered, the method Collections.max(). 我们将研究我们遇到的最微妙的案例之一,方法Collections.max()As we saw in section More Fun with Wildcards, a plausible signature for max() is:正如我们在“使用通配符更有趣”一节中看到的,max()的一个合理签名是:

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

This is fine, except that the erasure of this signature is:这是可以的,但删除此签名是:

public static Comparable max(Collection coll)

which is different than the original signature of max():max()的原始签名不同:

public static Object max(Collection coll)

One could certainly have specified this signature for max(), but it was not done, and all the old binary class files that call Collections.max() depend on a signature that returns Object.我们当然可以为max()指定这个签名,但没有这样做,所有调用Collections.max()的旧二进制类文件都依赖于返回Object的签名。

We can force the erasure to be different, by explicitly specifying a superclass in the bound for the formal type parameter T.我们可以通过在形式类型参数T的界中显式指定一个超类,强制擦除不同。

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

This is an example of giving multiple bounds for a type parameter, using the syntax T1 & T2 ... & Tn. A type variable with multiple bounds is known to be a subtype of all of the types listed in the bound. When a multiple bound is used, the first type mentioned in the bound is used as the erasure of the type variable.

Finally, we should recall that max only reads from its input collection, and so is applicable to collections of any subtype of T.

This brings us to the actual signature used in the JDK:这就引出了JDK中使用的实际签名:

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

It's very rare that anything so involved comes up in practice, but expert library designers should be prepared to think very carefully when converting existing APIs.实践中很少会出现这样的情况,但专家库设计师在转换现有API时应该准备好仔细考虑。

Another issue to watch out for is covariant returns, that is, refining the return type of a method in a subclass. 另一个需要注意的问题是协变返回,即在子类中细化方法的返回类型。You should not take advantage of this feature in an old API. 您不应该在旧API中利用此功能。To see why, let's look at an example.为了了解原因,我们来看一个例子。

Assume your original API was of the form:假设您的原始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() {
        ...
    }
}

Taking advantage of covariant returns, you modify it to:利用协变回报率,将其修改为:

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() {
        ...
    }
}

Now, assume a third party client of your code wrote the following:现在,假设您的代码的第三方客户端编写了以下代码:

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

The Java virtual machine does not directly support overriding of methods with different return types. Java虚拟机不直接支持重写具有不同返回类型的方法。This feature is supported by the compiler. Consequently, unless the class Baz is recompiled, it will not properly override the create() method of Bar. Furthermore, Baz will have to be modified, since the code will be rejected as written—the return type of create() in Baz is not a subtype of the return type of create() in Bar.


Previous page: More Fun with Wildcards
Next page: Acknowledgements