# 将旧版代码转换为使用泛型
# 入参转换
如果您决定将旧代码转换为使用泛型,则需要仔细考虑如何修改 API。
你必须确保泛型 API 不受限制,它必须支持原来的旧的调用。考虑一个示例:java.util.Collection
interface Collection {
public boolean containsAll(Collection c);
public boolean addAll(Collection c);
}
1
2
3
4
2
3
4
尝试把他修改为泛型的
interface Collection<E> {
public boolean containsAll(Collection<E> c);
public boolean addAll(Collection<E> c);
}
1
2
3
4
2
3
4
虽然这个 API 保证了类型的安全,对于 containsAll()
API 不符合原来的 API 了。
containsAll(Collection c)
:不同类型的集合是可以比较的,而新的必须要是两个 E 类型的集合- 传入不同类型的集合不行了,可能是因为调用方不知道传入的集合的确切类型,或者因为它是集合
<S>
,其中 S 是 E 的子类型。
对于 addAll
应该可以添加任何 E 类型的或 E 子类型的元素。这个在前面通用方法中讲解过了。
您还需要确保修订后的 API 保留与旧客户端的二进制兼容性。这意味着该 API 的擦除必须与原始的未经过增强的API 相同。在大多数情况下,这是自然而然的事情,但是有一些微妙的情况。我们将研究我们遇到的最微妙的情况之一:Collections.max()
,在前面也讲解过。合理的签名是
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
1
删除泛型后
public static Comparable max(Collection coll)
可以看到与原始 API 不同了
public static Object max(Collection coll)
1
2
3
4
5
2
3
4
5
所有旧的二进制文件依赖返回 Object 的方法签名。
这个时候我们可以使用显示指定 超类型来作为泛型变量的擦除
public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll)
1
已知具有多个界限的类型变量是界限中列出的所有类型的子类型,边界中第一个类型将用作类型变量的擦除
JDK 中该方法的实际签名是
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
1
# 返回参数类型转换
另一个问题是返回参数类型的优化,你 不应该在旧的 API 中利用泛型功能,下面我们来探讨下为什么
假设旧 API 为
public class Foo {
// 工厂方法,返回 Foo 或他的子类
public Foo create() {
...
}
}
public class Bar extends Foo {
// 重写后,实际返回 Bar 类型
public Foo create() {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
利用重载原理,我们可以把返回类型修改为实际的类型
public class Foo {
public Foo create() {
...
}
}
public class Bar extends Foo {
// 修改了返回类型
public Bar create() {
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
现在,假设您的代码的第三方客户端编写了以下内容:
public class Baz extends Bar {
// Actually creates a Baz.
public Foo create() {
...
}
}
1
2
3
4
5
6
2
3
4
5
6
Java 虚拟机 不直接支持具有不同返回类型的方法的覆盖。编译器支持此功能,因此 Baz 类必须重新,因为它不能覆盖 Bar 中的 create 方法,因为它返回的类型不是 Bar 中 create 返回的子类型。