|
| 1 | +## 具有传统客户端的通用库 |
| 2 | + |
| 3 | +现在让我们考虑一下这种情况,即在客户端保留其旧版本时,库更新为泛型。这可能是因为没有足够的时间一次转换所有内容,或者因为类库和客户由不同的组织控制。这对应于向后兼容性的最重要情况,其中 `Java 5` 的通用集合框架必须仍然可以与针对 `Java 1.4` 中的集合框架编写的传统客户端一起工作。 |
| 4 | + |
| 5 | +为了支持进化,每当定义参数化类型时,`Java` 也会识别相应的非参数化类型的类型,称为原始类型。例如,参数化类型 `Stack<E>` 对应于原始类型 `Stack`,参数化类型 `ArrayStack<E>` 对应于原始类型 `ArrayStack`。 |
| 6 | + |
| 7 | +每个参数化类型都是相应原始类型的子类型,因此可以在需要原始类型的位置传递参数化类型的值。通常,传递一个超类型值是一个错误,其中它的子类型的值是预期的,但是 `Java` 确实允许在需要参数化类型的地方传递一个原始类型的值 - 然而,它通过生成标志来标记这种情况未经检查的转换警告。例如,您可以将 `Stack<E>` 类型的值分配给 `Stack` 类型的变量,因为前者是后者的子类型。您也可以将 `Stack` 类型的值分配给 `Stack<E>` 类型的变量,但这会生成未经检查的转换警告。 |
| 8 | + |
| 9 | +具体来说,考虑从例 `5-2` 的 `Stack<E>`,`ArrayStack<E>` 和 `Stacks` 的通用源(例如,在目录 `g` 中)使用来自例 `5-1` 的 `Client` 的遗留源(例如,在目录 `l`)。 `Sun` 的 `Java 5` 编译器会产生以下消息: |
| 10 | + |
| 11 | +```java |
| 12 | + % javac g/Stack.java g/ArrayStack.java g/Stacks.java l/Client.java |
| 13 | + Note: Client.java uses unchecked or unsafe operations. |
| 14 | + Note: Recompile with -Xlint:unchecked for details. |
| 15 | +``` |
| 16 | + |
| 17 | +未经检查的警告表明,编译器无法提供与泛型在统一使用时相同的安全保证。 但是,当通过更新遗留代码生成通用代码时,我们知道从两者都生成了等效的类文件,因此(即使未经检查的警告)使用通用库运行旧版客户端将产生与运行旧版客户端相同的结果 与遗留图书馆。 在这里,我们假设更新图书馆的唯一改变是引入泛型,并且不管是故意还是错误地引入行为改变。 |
| 18 | + |
| 19 | +例 `5-2`。 具有通用客户端的通用库 |
| 20 | + |
| 21 | +```java |
| 22 | + g/Stack.java: |
| 23 | + interface Stack<E> { |
| 24 | + public boolean empty(); |
| 25 | + public void push(E elt); |
| 26 | + public E pop(); |
| 27 | + } |
| 28 | + |
| 29 | + g/ArrayStack.java: |
| 30 | + import java.util.*; |
| 31 | + class ArrayStack<E> implements Stack<E> { |
| 32 | + private List<E> list; |
| 33 | + public ArrayStack() { list = new ArrayList<E>(); } |
| 34 | + public boolean empty() { return list.size() == 0; } |
| 35 | + public void push(E elt) { list.add(elt); } |
| 36 | + public E pop() { |
| 37 | + E elt = list.remove(list.size()-1); |
| 38 | + return elt; |
| 39 | + } |
| 40 | + public String toString() { return "stack"+list.toString(); } |
| 41 | + } |
| 42 | + |
| 43 | + g/Stacks.java: |
| 44 | + class Stacks { |
| 45 | + public static <T> Stack<T> reverse(Stack<T> in) { |
| 46 | + Stack<T> out = new ArrayStack<T>(); |
| 47 | + while (!in.empty()) { |
| 48 | + T elt = in.pop(); |
| 49 | + out.push(elt); |
| 50 | + } |
| 51 | + return out; |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + g/Client.java: |
| 56 | + class Client { |
| 57 | + public static void main(String[] args) { |
| 58 | + Stack<Integer> stack = new ArrayStack<Integer>(); |
| 59 | + for (int i = 0; i<4; i++) stack.push(i); |
| 60 | + assert stack.toString().equals("stack[0, 1, 2, 3]"); |
| 61 | + int top = stack.pop(); |
| 62 | + assert top == 3 && stack.toString().equals("stack[0, 1, 2]"); |
| 63 | + Stack<Integer> reverse = Stacks.reverse(stack); |
| 64 | + assert stack.empty(); |
| 65 | + assert reverse.toString().equals("stack[2, 1, 0]"); |
| 66 | + } |
| 67 | + } |
| 68 | +``` |
| 69 | + |
| 70 | +如果我们遵循上面的建议,并在启用适当的开关的情况下重新运行编译器,我们会得到更多的细节: |
| 71 | + |
| 72 | +```java |
| 73 | + % javac -Xlint:unchecked g/Stack.java g/ArrayStack.java \ |
| 74 | + % g/Stacks.java l/Client.java |
| 75 | + l/Client.java:4: warning: [unchecked] unchecked call |
| 76 | + to push(E) as a member of the raw type Stack |
| 77 | + for (int i = 0; i<4; i++) stack.push(new Integer(i)); |
| 78 | + ^ |
| 79 | + l/Client.java:8: warning: [unchecked] unchecked conversion |
| 80 | + found : Stack |
| 81 | + required: Stack<E> |
| 82 | + Stack reverse = Stacks.reverse(stack); |
| 83 | + ^ |
| 84 | + l/Client.java:8: warning: [unchecked] unchecked method invocation: |
| 85 | + <E>reverse(Stack<E>) in Stacks is applied to (Stack) |
| 86 | + Stack reverse = Stacks.reverse(stack); |
| 87 | + ^ |
| 88 | + 3 warnings |
| 89 | +``` |
| 90 | + |
| 91 | +并非每种原始类型都会引发警告。因为每个参数化类型都是相应原始类型的子类型,但是相反,传递一个参数化类型(其中原始类型是预期的)是安全的(因此,没有警告来获得反向结果),但是将原始类型传递给参数化预期类型会发出警告(因此,传递参数时会发出警告);这是替代原则的一个例子。当我们在原始类型的接收者上调用方法时,该方法被视为类型参数是通配符,因此从原始类型获取值是安全的(因此,不会弹出调用 `pop` 的警告),而是将原始类型的值会发出警告(因此,调用 `push` 的警告);这是获取和放置原则的一个实例。 |
| 92 | + |
| 93 | +即使您没有编写任何通用代码,您仍可能会遇到进化问题,因为其他人已经使其代码生成了基因。这会影响使用已由 `Sun` 进行基因化的集合框架的遗留代码的所有人。因此,在旧版客户端中使用泛型库最重要的情况是使用 `Java 5` 集合框架和为 `Java 1.4` 集合框架编写的遗留代码。 |
| 94 | + |
| 95 | +特别是,在示例 `5-1` 中将 `Java 5` 编译器应用于遗留代码时,也会发出未经检查的警告,因为传统类 `ArrayStack` 中使用了基本化的类 `ArrayList`。下面是当我们用 `Java 5` 编译器和库编译所有文件的旧版本时发生的情况: |
| 96 | + |
| 97 | +```java |
| 98 | + % javac -Xlint:unchecked l/Stack.java l/ArrayStack.java \ |
| 99 | + % l/Stacks.java l/Client.java |
| 100 | + l/ArrayStack.java:6: warning: [unchecked] unchecked call to add(E) |
| 101 | + as a member of the raw type java.util.List |
| 102 | + public void push(Object elt) list.add(elt); |
| 103 | + ^ |
| 104 | + 1 warning |
| 105 | +``` |
| 106 | + |
| 107 | +在这里,传统方法 `push` 中使用泛型方法 `add` 的警告是由于类似于从旧客户端发出使用泛型方法 `push` 的先前警告的原因而发布的。 |
| 108 | + |
| 109 | +将编译器配置为反复发出您打算忽略的警告是一种很不好的做法。 这会让人分心,更糟的是,它可能会导致你忽视需要注意的警告 - 就像在狼的小男孩的寓言中一样。 在纯代码的情况下,可以使用 `-source 1.4` 开关关闭此类警告: |
| 110 | + |
| 111 | +```java |
| 112 | + % javac -source 1.4 l/Stack.java l/ArrayStack.java \ |
| 113 | + % l/Stacks.java l/Client.java |
| 114 | +``` |
| 115 | + |
| 116 | +这编译了遗留代码并且没有发出警告或错误。 这种关闭警告的方法只适用于真正的遗留代码,没有 `Java 5` 中引入的通用功能或其他功能。 也可以使用注释关闭未经检查的警告,如下一节所述,即使使用 `Java 5` 中引入的功能,也可以使用这些警告。 |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | + |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | + |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | + |
| 174 | + |
| 175 | + |
| 176 | + |
| 177 | + |
| 178 | + |
| 179 | + |
| 180 | + |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | + |
| 185 | + |
| 186 | + |
| 187 | + |
| 188 | + |
| 189 | + |
| 190 | + |
| 191 | + |
| 192 | + |
| 193 | + |
| 194 | + |
| 195 | + |
0 commit comments