Skip to content

Commit 798f5d4

Browse files
committed
add
Signed-off-by: maskleo <[email protected]>
1 parent 243433b commit 798f5d4

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed

Diff for: ch08/04_Maintain_Binary_Compatibility.md

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
## 保持二进制兼容性
2+
3+
正如我们强调的那样,泛型是通过擦除来实现的,以缓解进化。当将遗留代码转化为泛型代码时,我们希望确保新生代码能够与任何现有代码一起工作,包括我们没有源代码的类文件。当这种情况发生时,我们说传统和通用版本是二进制兼容的。
4+
5+
如果擦除通用代码的签名与遗留代码的签名相同,并且两个版本都编译为相同的字节代码,则可以保证二进制兼容性。通常情况下,这是自然发生的自然结果,但在本节中,我们将看到一些可能导致问题的角落案例。
6+
7+
本节的一些示例摘自Mark Reinhold编写的内部Sun笔记。
8+
9+
调整擦除与集合类中的max方法的生成有关的一个边角情况出现了。我们在第3.2节和第3.6节中讨论了这种情况,但值得快速回顾一下。
10+
11+
这是这种方法的遗留签名:
12+
13+
```java
14+
// 旧版本
15+
public static Object max(Collection coll)
16+
```
17+
18+
这里是自然的通用签名,使用通配符来获得最大的灵活性(参见第 `3.2` 节):
19+
20+
```java
21+
// 通用版本 - 打破二进制兼容性
22+
public static <T extends Comparable<? super T>>
23+
T max(Collection<? extends T> coll)
24+
```
25+
26+
但是这个签名有错误的擦除 - 它的返回类型是 `Comparable` 而不是 `Object`。 为了获得正确的签名,我们需要使用多重边界来摆弄类型参数的边界(参见第 `3.6` 节)。 这是更正后的版本:
27+
28+
```java
29+
// 通用版本 - 保持二进制兼容性
30+
public static <T extends Object & Comparable<? super T>>
31+
T max(Collection<? extends T> coll)
32+
```
33+
34+
当有多个边界时,最左边界被用于擦除。 所以 `T` 的删除现在是 `Object`,给出了我们需要的结果类型。
35+
36+
由于原始遗留代码包含的特定类型比它可能具有的特定类型要少,因此会出现一些与遗传有关的问题。 例如,`max` 的遗留版本可能已被赋予返回类型 `Comparable`,它比 `Object` 更具体,然后就不需要使用多重边界来调整类型。
37+
38+
桥梁另一个重要的角落案例与桥梁有关。 同样,`Comparable` 提供了一个很好的例子。实现 `Comparable` 的大多数遗留核心类提供了 `compareTo` 方法的两个重载:一个带有参数类型 `Object`,它重写接口中的 `compareTo` 方法; 和一个更具体的类型。 例如,以下是 `Integer` 旧版本的相关部分:
39+
40+
```java
41+
// 旧版本
42+
public class Integer implements Comparable {
43+
public int compareTo(Object o) { ... }
44+
public int compareTo(Integer i) { ... }
45+
...
46+
}
47+
```
48+
49+
这里是相应的通用版本:
50+
51+
```java
52+
// 通用版本 - 保持二进制兼容性
53+
public final class Integer implements Comparable<Integer> {
54+
public int compareTo(Integer i) { ... }
55+
...
56+
}
57+
```
58+
59+
两个版本都具有相同的字节码,因为编译器会为 `compareTo``Object` 类型的参数生成一个桥接方法(请参阅第 `3.7` 节)。
60+
61+
但是,一些遗留代码仅包含 `Object` 方法。 (在泛型之前,一些程序员认为这比定义两种方法更简洁。)下面是 `javax.naming.Name` 的传统版本.
62+
63+
```java
64+
// 旧版本
65+
public interface Name extends Comparable {
66+
public int compareTo(Object o);
67+
...
68+
}
69+
```
70+
71+
事实上,名称只与其他名称进行比较,所以我们可能希望以下通用版本:
72+
73+
```java
74+
// 通用版本 - 打破二进制兼容性
75+
public interface Name extends Comparable<Name> {
76+
public int compareTo(Name n);
77+
...
78+
}
79+
```
80+
81+
但是,选择这种生成功能会破坏二进制兼容性。 由于遗留类包含 `compareTo(Object)` 而不是 `compareTo(Name)`,因此很可能用户可能已声明 `Name` 的实现提供前者而不是后者。 任何这样的类都不适用于上面给出的通用版本的名称。 唯一的解决办法是选择一个不那么雄心勃勃的基因工程:
82+
83+
```java
84+
// 通用版本 - 保持二进制兼容性
85+
public interface Name extends Comparable<Object> {
86+
public int compareTo(Object o) { ... }
87+
...
88+
}
89+
```
90+
91+
这与旧版本有相同的擦除,并保证与用户可能已定义的任何子类兼容。
92+
93+
在前面的例子中,如果选择了更加雄心勃勃的基因鉴定,那么在运行时会出现错误,因为实现类没有实现 `compareTo(Name)`
94+
95+
但是在某些情况下,这种差异可能是阴险的:与其提出错误,可能会返回不同的值!例如,`Name` 可以通过 `SimpleName` 类来实现,其中一个简单名称由单个字符串,`base` 组成,并且比较两个简单名称比较基本名称。进一步说,`SimpleName` 有一个扩展名的子类,其中扩展名有一个基本字符串和一个扩展名。将扩展名与简单名称进行比较时只比较基本名称,而将扩展名称与另一扩展名称进行比较,比较基数,如果相等,则比较扩展名。假设我们 `Generify Name``SimpleName`,以便它们定义 `compareTo(Name)`,但我们没有 `ExtendedName` 的源。由于它只定义了 `compareTo(Object)`,所以调用 `compareTo(Name)` 而不是 `compareTo(Object)` 的客户端代码将在 `SimpleName`(定义它的位置)而不是 `ExtendedName`(它未定义的地方)上调用该方法,将被比较,但扩展忽略。这在示例 `8-2` 和示例 `8-3` 中进行了说明。
96+
97+
我们得到的教训是,除非您有信心可以兼容所有亚类,否则每次生成一个班时都要特别小心。请注意,如果将一个类声明为 `final` ,那么您有更多的余地,因为它不能有子类。
98+
99+
还要注意,如果原始的 `Name` 接口不仅声明了一般重载 `compareTo(Object)`,还声明了更具体的重载 `compareTo(Name)`,那么将需要旧版本的 `SimpleName``ExtendedName` 来实现 `compareTo(Name)` 和这里描述的问题不会出现。
100+
101+
**协变覆盖**另一个角落案例与协变覆盖有关(见 `3.8` 节)。 回想一下,如果参数完全匹配,则一个方法可以重写另一个方法,但重写方法的返回类型是另一个方法的返回类型的子类型。
102+
103+
这是克隆方法的一个应用:
104+
105+
```java
106+
class Object {
107+
public Object clone() { ... }
108+
...
109+
}
110+
```
111+
112+
以下是类 `HashSet` 的旧版本:
113+
114+
```java
115+
// 旧版本
116+
class HashSet {
117+
public Object clone() { ... }
118+
...
119+
}
120+
```
121+
122+
对于通用版本,您可能希望利用协变覆盖并为克隆选择更具体的返回类型:
123+
124+
```java
125+
// 通用版本 - 打破二进制兼容性
126+
class HashSet {
127+
public HashSet clone() { ... }
128+
...
129+
}
130+
```
131+
132+
`8-2`。 用于简单和扩展名称的传统代码
133+
134+
```java
135+
interface Name extends Comparable {
136+
public int compareTo(Object o);
137+
}
138+
class SimpleName implements Name {
139+
private String base;
140+
public SimpleName(String base) {
141+
this.base = base;
142+
}
143+
public int compareTo(Object o) {
144+
return base.compareTo(((SimpleName)o).base);
145+
}
146+
}
147+
class ExtendedName extends SimpleName {
148+
private String ext;
149+
public ExtendedName(String base, String ext) {
150+
super(base); this.ext = ext;
151+
}
152+
public int compareTo(Object o) {
153+
int c = super.compareTo(o);
154+
if (c == 0 && o instanceof ExtendedName)
155+
return ext.compareTo(((ExtendedName)o).ext);
156+
else
157+
return c;
158+
}
159+
}
160+
class Client {
161+
public static void main(String[] args) {
162+
Name m = new ExtendedName("a","b");
163+
Name n = new ExtendedName("a","c");
164+
assert m.compareTo(n) < 0;
165+
}
166+
}
167+
```
168+
169+
`8-3`。 生成简单的名称和客户端,但不扩展名称
170+
171+
```java
172+
interface Name extends Comparable<Name> {
173+
public int compareTo(Name o);
174+
}
175+
class SimpleName implements Name {
176+
private String base;
177+
public SimpleName(String base) {
178+
this.base = base;
179+
}
180+
public int compareTo(Name o) {
181+
return base.compareTo(((SimpleName)o).base);
182+
}
183+
}
184+
185+
// use legacy class file for ExtendedName
186+
class Test {
187+
public static void main(String[] args) {
188+
Name m = new ExtendedName("a","b");
189+
Name n = new ExtendedName("a","c");
190+
assert m.compareTo(n) == 0; // 答案现在不同!
191+
}
192+
}
193+
```
194+
195+
但是,选择这种生成功能会破坏二进制兼容性。 用户很可能已经定义了覆盖克隆的 `HashSet` 的子类。 任何这样的子类都不适用于之前给出的 `HashSet` 的通用版本。 唯一的解决办法是选择一个不那么雄心勃勃的基因工程:
196+
197+
```java
198+
// 通用版本 - 保持二进制兼容性
199+
class HashSet {
200+
public Object clone() { ... }
201+
...
202+
}
203+
```
204+
205+
这保证与用户可能定义的任何子类兼容。 同样,如果你还可以生成任何子类,或者如果这个类是最终的,那么你有更多的自由。
206+
207+
208+
209+
210+
211+
212+
213+
214+
215+
216+
217+
218+
219+
220+
221+
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+
238+
239+
240+
241+
242+
243+
244+
245+
246+
247+
248+
249+
250+
251+
252+
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
263+
264+

0 commit comments

Comments
 (0)