|
9 | 9 | 在这种情况下,更新库以在其方法签名中使用参数化类型是有意义的,但不能更改方法体。 有三种方法可以实现这一点:对源代码进行最小限度的更改,创建存根文件或
|
10 | 10 | 使用包装器。我们建议在仅有权访问类时有权访问源代码和使用存根时使用最少的更改 文件,我们建议不要使用包装。
|
11 | 11 |
|
12 |
| - |
13 |
| -### 用最小的变化来演变一个类库 |
| 12 | +#### 用最小的变化来演变一个类库 |
14 | 13 |
|
15 | 14 | 示例 `5-3` 显示了最小更改技术。 这里库的来源已经被编辑过,但只是为了改变方法签名,而不是方法体。 所需的确切更改以粗体显示。 当您有权访问源时,推荐使
|
16 | 15 | 用这种技术来使库变得通用。
|
17 | 16 |
|
18 | 17 | 确切地说,所需的改变是:
|
19 | 18 |
|
20 |
| - - 根据需要为接口或类声明添加类型参数(对于接口 `Stack<E>` 和类 `ArrayStack<E>`) |
21 |
| - |
22 |
| - - 将类型参数添加到扩展或实现子句中的任何新参数化接口或类(对 `ArrayStack<E>` 的 `implements` 子句中的 `Stack<E>`), |
23 |
| - |
24 |
| - - 根据需要为每个方法签名添加类型参数(用于在 `Stack<E>` 和 `ArrayStack<E>` 中进行推入和弹出操作,并在堆栈中进行反向操作) |
25 |
| - |
26 |
| - - 在返回类型包含一个类型参数(对于在 `ArrayStack<E>` 中弹出,其中返回类型为 `E`)的每个返回中添加一个未经检查的强制转换 - 没有此强制转换的情况 |
27 |
| - 下,您将得到一个错误而不是未经检查的警告 |
28 |
| - |
29 |
| - - 可选择添加批注以抑制未经检查的警告(对于 `ArrayStack<E>` 和 `Stacks`) |
| 19 | +- 根据需要为接口或类声明添加类型参数(对于接口 `Stack<E>` 和类 `ArrayStack<E>`) |
| 20 | + |
| 21 | +- 将类型参数添加到扩展或实现子句中的任何新参数化接口或类(对 `ArrayStack<E>` 的 `implements` 子句中的 `Stack<E>`), |
| 22 | + |
| 23 | +- 根据需要为每个方法签名添加类型参数(用于在 `Stack<E>` 和 `ArrayStack<E>` 中进行推入和弹出操作,并在堆栈中进行反向操作) |
| 24 | + |
| 25 | +- 在返回类型包含一个类型参数(对于在 `ArrayStack<E>` 中弹出,其中返回类型为 `E`)的每个返回中添加一个未经检查的强制转换 - 没有此强制转换的情况 |
| 26 | +下,您将得到一个错误而不是未经检查的警告 |
| 27 | + |
| 28 | +- 可选择添加批注以抑制未经检查的警告(对于 `ArrayStack<E>` 和 `Stacks`) |
30 | 29 |
|
31 | 30 | 值得注意的是我们不需要做出一些改变。 在方法体中,我们可以保留 `Object` 的出现(参见 `ArrayStack` 中的第一行 `pop`),并且我们不需要为任何出现的raw
|
32 | 31 | 类型添加类型参数(请参阅 `Stacks` 中的第一行)。 此外,只有当返回类型是类型参数(如 `pop` 中)时,我们才需要将转换添加到 `return` 子句中,但是当返
|
|
69 | 68 | 例 `5-3`。 使用最小的变化来发展一个类库
|
70 | 69 |
|
71 | 70 | ```
|
72 |
| - m/Stack.java: |
73 |
| - interface Stack<E> { |
74 |
| - public boolean empty(); |
75 |
| - public void push(E elt); |
76 |
| - public E pop(); |
77 |
| - } |
78 |
| - |
79 |
| - m/ArrayStack.java: |
80 |
| - @SuppressWarnings("unchecked") |
81 |
| - class ArrayStack<E> implements Stack<E> { |
82 |
| - private List list; |
83 |
| - public ArrayStack() { list = new ArrayList(); } |
84 |
| - public boolean empty() { return list.size() == 0; } |
85 |
| - public void push(E elt) { list.add(elt); } // unchecked call |
86 |
| - public E pop() { |
87 |
| - Object elt = list.remove(list.size()-1); |
88 |
| - return (E)elt; // unchecked cast |
| 71 | +m/Stack.java: |
| 72 | +interface Stack<E> { |
| 73 | + public boolean empty(); |
| 74 | + public void push(E elt); |
| 75 | + public E pop(); |
| 76 | +} |
| 77 | +
|
| 78 | +m/ArrayStack.java: |
| 79 | + @SuppressWarnings("unchecked") |
| 80 | + class ArrayStack<E> implements Stack<E> { |
| 81 | + private List list; |
| 82 | + public ArrayStack() { list = new ArrayList(); } |
| 83 | + public boolean empty() { return list.size() == 0; } |
| 84 | + public void push(E elt) { list.add(elt); } // unchecked call |
| 85 | + public E pop() { |
| 86 | + Object elt = list.remove(list.size()-1); |
| 87 | + return (E)elt; // unchecked cast |
| 88 | +} |
| 89 | + public String toString() { return "stack"+list.toString(); } |
| 90 | +} |
| 91 | +
|
| 92 | +m/Stacks.java: |
| 93 | +@SuppressWarnings("unchecked") |
| 94 | +class Stacks { |
| 95 | + public static <T> Stack<T> reverse(Stack<T> in) { |
| 96 | + Stack out = new ArrayStack(); |
| 97 | + while (!in.empty()) { |
| 98 | + Object elt = in.pop(); |
| 99 | + out.push(elt); // unchecked call |
89 | 100 | }
|
90 |
| - public String toString() { return "stack"+list.toString(); } |
91 |
| - } |
92 |
| - |
93 |
| - m/Stacks.java: |
94 |
| - @SuppressWarnings("unchecked") |
95 |
| - class Stacks { |
96 |
| - public static <T> Stack<T> reverse(Stack<T> in) { |
97 |
| - Stack out = new ArrayStack(); |
98 |
| - while (!in.empty()) { |
99 |
| - Object elt = in.pop(); |
100 |
| - out.push(elt); // unchecked call |
101 |
| - } |
102 |
| - return out; // unchecked conversion |
103 |
| - } |
104 |
| - } |
| 101 | + return out; // unchecked conversion |
| 102 | + } |
| 103 | +} |
105 | 104 | ```
|
106 | 105 |
|
107 | 106 | 消除(而不是抑制)编译库生成的未经检查的警告的唯一方法是更新整个库源以使用泛型。 这是完全合理的,因为除非更新整个源代码,否则编译器无法检查声明的通用
|
|
111 | 110 | 例 `5-4`。 使用存根发展类库
|
112 | 111 |
|
113 | 112 | ```java
|
114 |
| - s/Stack.java: |
115 |
| - interface Stack<E> { |
116 |
| - public boolean empty(); |
117 |
| - public void push(E elt); |
118 |
| - public E pop(); |
119 |
| - } |
120 |
| - |
121 |
| - s/StubException.java: |
122 |
| - class StubException extends UnsupportedOperationException {} |
123 |
| - |
124 |
| - s/ArrayStack.java: |
125 |
| - class ArrayStack<E> implements Stack<E> { |
126 |
| - public boolean empty() { throw new StubException(); } |
127 |
| - public void push(E elt) { throw new StubException(); } |
128 |
| - public E pop() { throw new StubException(); } |
129 |
| - public String toString() { throw new StubException(); } |
130 |
| - } |
131 |
| - |
132 |
| - s/Stacks.java: |
133 |
| - class Stacks { |
134 |
| - public static <T> Stack<T> reverse(Stack<T> in) { |
135 |
| - throw new StubException(); |
136 |
| - } |
137 |
| - } |
| 113 | +s/Stack.java: |
| 114 | +interface Stack<E> { |
| 115 | + public boolean empty(); |
| 116 | + public void push(E elt); |
| 117 | + public E pop(); |
| 118 | +} |
| 119 | + |
| 120 | +s/StubException.java: |
| 121 | +class StubException extends UnsupportedOperationException {} |
| 122 | + |
| 123 | +s/ArrayStack.java: |
| 124 | +class ArrayStack<E> implements Stack<E> { |
| 125 | + public boolean empty() { |
| 126 | + throw new StubException(); |
| 127 | + } |
| 128 | + public void push(E elt) { |
| 129 | + throw new StubException(); |
| 130 | + } |
| 131 | + public E pop() { |
| 132 | + throw new StubException(); |
| 133 | + } |
| 134 | + public String toString() { |
| 135 | + throw new StubException(); |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +s/Stacks.java: |
| 140 | +class Stacks { |
| 141 | + public static <T> Stack<T> reverse(Stack<T> in) { |
| 142 | + throw new StubException(); |
| 143 | + } |
| 144 | +} |
138 | 145 | ```
|
139 | 146 |
|
140 |
| -### 使用存根演化库 |
| 147 | +#### 使用存根演化库 |
141 | 148 |
|
142 | 149 | 示例 `5-4` 中显示了存根技术。 在这里,我们编写了具有通用签名但不包含 `body` 的存根。我们针对通用签名编译通用客户端,但针对遗留类文件运行代码。 这种
|
143 | 150 | 技术适用于源未发布或其他人负责维护源的情况。
|
|
149 | 156 | 件(比如在目录 `l` 中)进行这种操作。
|
150 | 157 |
|
151 | 158 | ```java
|
152 |
| - % javac -classpath s g/Client.java |
153 |
| - % java -ea -classpath l g/Client |
| 159 | +% javac -classpath s g/Client.java |
| 160 | +% java -ea -classpath l g/Client |
154 | 161 | ```
|
155 | 162 |
|
156 | 163 | 再说一遍,这是有效的,因为为传统文件和通用文件生成的类文件基本相同,除了关于类型的辅助信息。 特别是,客户端编译的通用签名与传统签名(除了关于类型参数
|
157 | 164 | 的辅助信息)相匹配,因此代码可以成功运行并提供与以前相同的答案。
|
158 | 165 |
|
159 |
| -### 使用包装进化库 |
| 166 | +#### 使用包装进化库 |
160 | 167 |
|
161 | 168 | 例 `5-5` 给出了这个包装技术。 在这里,我们保留原有的源文件和类文件不变,并提供通过代理访问遗留类的通用包装类。我们主要介绍这种技术,主要是为了警告您
|
162 | 169 | 不要使用它 - 通常最好使用最少的更改或存根。
|
|
188 | 195 | 例 `5-5`。 使用包装器发展一个库
|
189 | 196 |
|
190 | 197 | ```java
|
191 |
| - //不要这样做---不推荐使用包装类 |
192 |
| - l/Stack.java, l/Stacks.java, l/ArrayStack.java: |
193 |
| - // As in Example 5.1 |
194 |
| - w/GenericStack.java: |
195 |
| - interface GenericStack<E> { |
196 |
| - public Stack unwrap(); |
197 |
| - public boolean empty(); |
198 |
| - public void push(E elt); |
199 |
| - public E pop(); |
200 |
| - } |
201 |
| - w/GenericStackWrapper.java: |
202 |
| - @SuppressWarnings("unchecked") |
203 |
| - class GenericStackWrapper<E> implements GenericStack<E> { |
204 |
| - private Stack stack; |
205 |
| - public GenericStackWrapper(Stack stack) { this.stack = stack; } |
206 |
| - public Stack unwrap() { return stack; } |
207 |
| - public boolean empty() { return stack.empty(); } |
208 |
| - public void push(E elt) { stack.push(elt); } |
209 |
| - public E pop() { return (E)stack.pop(); } // unchecked cast |
210 |
| - public String toString() { return stack.toString(); } |
211 |
| - } |
212 |
| - w/GenericStacks.java: |
213 |
| - class GenericStacks { |
214 |
| - public static <T> GenericStack<T> reverse(GenericStack<T> in) { |
215 |
| - Stack rawIn = in.unwrap(); |
216 |
| - Stack rawOut = Stacks.reverse(rawIn); |
217 |
| - return new GenericStackWrapper<T>(rawOut); |
218 |
| - } |
219 |
| - } |
220 |
| - w/Client.java: |
221 |
| - class Client { |
222 |
| - public static void main(String[] args) { |
223 |
| - GenericStack<Integer> stack = new GenericStackWrapper<Integer>(new ArrayStack()); |
224 |
| - for (int i = 0; i<4; i++) stack.push(i); |
225 |
| - assert stack.toString().equals("stack[0, 1, 2, 3]"); |
226 |
| - int top = stack.pop(); |
227 |
| - assert top == 3 && stack.toString().equals("stack[0, 1, 2]"); |
228 |
| - GenericStack<Integer> reverse = GenericStacks.reverse(stack); |
229 |
| - assert stack.empty(); |
230 |
| - assert reverse.toString().equals("stack[2, 1, 0]"); |
231 |
| - } |
232 |
| - } |
| 198 | +//不要这样做---不推荐使用包装类 |
| 199 | +l/Stack.java, l/Stacks.java, l/ArrayStack.java: |
| 200 | +// As in Example 5.1 |
| 201 | +w/GenericStack.java: |
| 202 | +interface GenericStack<E> { |
| 203 | + public Stack unwrap(); |
| 204 | + public boolean empty(); |
| 205 | + public void push(E elt); |
| 206 | + public E pop(); |
| 207 | +} |
| 208 | +w/GenericStackWrapper.java: |
| 209 | +@SuppressWarnings("unchecked") |
| 210 | +class GenericStackWrapper<E> implements GenericStack<E> { |
| 211 | + private Stack stack; |
| 212 | + public GenericStackWrapper(Stack stack) { |
| 213 | + this.stack = stack; |
| 214 | + } |
| 215 | + public Stack unwrap() { |
| 216 | + return stack; |
| 217 | + } |
| 218 | + public boolean empty() { |
| 219 | + return stack.empty(); |
| 220 | + } |
| 221 | + public void push(E elt) { |
| 222 | + stack.push(elt); |
| 223 | + } |
| 224 | + public E pop() { |
| 225 | + return (E)stack.pop(); |
| 226 | + } // unchecked cast |
| 227 | + public String toString() { |
| 228 | + return stack.toString(); |
| 229 | + } |
| 230 | +} |
| 231 | +w/GenericStacks.java: |
| 232 | +class GenericStacks { |
| 233 | + public static <T> GenericStack<T> reverse(GenericStack<T> in) { |
| 234 | + Stack rawIn = in.unwrap(); |
| 235 | + Stack rawOut = Stacks.reverse(rawIn); |
| 236 | + return new GenericStackWrapper<T>(rawOut); |
| 237 | + } |
| 238 | +} |
| 239 | +w/Client.java: |
| 240 | +class Client { |
| 241 | + public static void main(String[] args) { |
| 242 | + GenericStack<Integer> stack = new GenericStackWrapper<Integer>(new ArrayStack()); |
| 243 | + for (int i = 0; i<4; i++) |
| 244 | + stack.push(i); |
| 245 | + assert stack.toString().equals("stack[0, 1, 2, 3]"); |
| 246 | + int top = stack.pop(); |
| 247 | + assert top == 3 && stack.toString().equals("stack[0, 1, 2]"); |
| 248 | + GenericStack<Integer> reverse = GenericStacks.reverse(stack); |
| 249 | + assert stack.empty(); |
| 250 | + assert reverse.toString().equals("stack[2, 1, 0]"); |
| 251 | + } |
| 252 | +} |
233 | 253 | ```
|
234 | 254 |
|
235 | 255 | 包装也呈现更深和更微妙的问题。 如果代码使用对象标识,则可能会出现问题,因为遗留对象和包装对象是不同的。 此外,复杂的结构将需要多层包装纸。 想象一下,
|
|
0 commit comments