Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions ch01/00_Introduction.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](../Preface.md)


## 第一章(简介)

`Java` 最新版本中现在对泛型和集合与许多其他新功能有良好的支持,包括装箱和拆箱,新的循环形式,以及接受可变数量参数的函数。我们从一个例子开始说明了这
Expand All @@ -10,10 +9,12 @@
因此作为我们的座右铭,让我们做一些简单求和:把三个数字一个列表并将它们加在一起。 下面是如何在 `Java` 中使用泛型:  

```java
List<Integer> ints = Arrays.asList(1,2,3);
int s = 0;
for (int n : ints) { s += n; }
assert s == 6;
List<Integer> ints = Arrays.asList(1,2,3);
int s = 0;
for (int n : ints) {
s += n;
}
assert s == 6;
```

不需要太多的解释你就可以读懂代码,但是让我们来看看关键特征。接口列表和类数组是集合框架的一部分(都可以在 `java.util` 包中找到)。类型 `List` 现在是
Expand All @@ -25,15 +26,15 @@
下面是在泛型之前 `Java` 中相同作用的代码:

```java
List ints = Arrays.asList( new Integer[] {
new Integer(1), new Integer(2), new Integer(3)
} );
int s = 0;
for (Iterator it = ints.iterator(); it.hasNext(); ) {
List ints = Arrays.asList( new Integer[] {
new Integer(1), new Integer(2), new Integer(3)
} );
int s = 0;
for (Iterator it = ints.iterator(); it.hasNext(); ) {
int n = ((Integer)it.next()).intValue();
s += n;
}
assert s == 6;
}
assert s == 6;
```

阅读这段代码并不是那么容易。 没有泛型,就没有办法指出类型声明你打算在列表中存储什么样的元素,所以而不是写 `List<Integer>`,你写 `List`。  现在是
Expand All @@ -44,10 +45,12 @@
顺便说一句,下面是如何在泛型之前用 `Java` 中的数组做同样的事情:

```java
int[] ints = new int[] { 1,2,3 };
int s = 0;
for (int i = 0; i < ints.length; i++) { s += ints[i]; }
assert s == 6;
int[] ints = new int[] { 1,2,3 };
int s = 0;
for (int i = 0; i < ints.length; i++) {
s += ints[i];
}
assert s == 6;
```

这比使用泛型和集合的相应代码略长,可以说是不太可读,而且肯定不够灵活。 集合让你轻松增大或缩小集合的大小,或在切换到适当的不同的表示形式时,如链表或散
Expand Down
1 change: 0 additions & 1 deletion ch04/03_Nested_Classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,3 @@

《《《 [下一节](04_How_Erasure_Works.md) <br/>
《《《 [返回首页](../README.md)

6 changes: 5 additions & 1 deletion ch04/04_How_Erasure_Works.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](03_Nested_Classes.md)

## 擦除的工作原理

擦除类型的定义如下:从参数化类型中删除所有类型参数,并用删除它的边界来替换任何类型变量,或者如果它没有边界,则使用 `Object`;或者如果它具有最左边界
Expand Down Expand Up @@ -99,4 +102,5 @@

如果这得到支持,通常需要对桥接方法进行复杂而混乱的定义(参见第 `3.7` 节)。 到目前为止,最简单和最容易理解的选择是禁止这种情况。


《《《 [下一节](../ch05/00_Evolution_Not_Revolution.md) <br/>
《《《 [返回首页](../README.md)
28 changes: 21 additions & 7 deletions ch05/00_Evolution_Not_Revolution.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](../ch04/04_How_Erasure_Works.md)

## 进化,而不是革命

支持 `Java` 泛型设计的一个格言是进化,而不是革命。必须有可能迁移大量现有的代码,逐步使用泛型(演化),而不需要进行一次性的全面变革(革命)。泛型设计可确保旧代码针对新的 `Java` 库进行编译,避免了一半代码需要旧库和一半代码需要新库的不幸情况。
支持 `Java` 泛型设计的一个格言是进化,而不是革命。必须有可能迁移大量现有的代码,逐步使用泛型(演化),而不需要进行一次性的全面变革(革命)。泛型设计可确
保旧代码针对新的 `Java` 库进行编译,避免了一半代码需要旧库和一半代码需要新库的不幸情况。

进化要求比通常的向后兼容要强得多。通过简单的向后兼容性,可以为每个应用程序提供传统版本和通用版本;例如,这正是 `C#` 中发生的情况。如果您正在构建由多个供应商提供的代码之上,其中一些人使用旧版集合,其中一些人使用通用集合,这可能会迅速导致版本化的噩梦。
进化要求比通常的向后兼容要强得多。通过简单的向后兼容性,可以为每个应用程序提供传统版本和通用版本;例如,这正是 `C#` 中发生的情况。如果您正在构建由多个供
应商提供的代码之上,其中一些人使用旧版集合,其中一些人使用通用集合,这可能会迅速导致版本化的噩梦。

我们要求的是相同的客户端代码可以同时适用于库的传统版本和通用版本。这意味着图书馆的供应商和客户可以完全独立地选择何时从传统代码转换为通用代码。这比后向兼容性要求强得多;它被称为迁移兼容性或平台兼容性。
我们要求的是相同的客户端代码可以同时适用于库的传统版本和通用版本。这意味着图书馆的供应商和客户可以完全独立地选择何时从传统代码转换为通用代码。这比后向兼
容性要求强得多;它被称为迁移兼容性或平台兼容性。

`Java` 通过擦除来实现泛型,这可以确保传统版本和通用版本通常会生成相同的类文件,并保存一些有关类型的辅助信息。可以用通用类文件替换旧类文件而不更改甚至重新编译任何客户端码;这被称为二进制兼容性。
`Java` 通过擦除来实现泛型,这可以确保传统版本和通用版本通常会生成相同的类文件,并保存一些有关类型的辅助信息。可以用通用类文件替换旧类文件而不更改甚至重
新编译任何客户端码;这被称为二进制兼容性。

我们总结这与座右铭二进制兼容性确保迁移兼容性- 或者更简洁一点,擦除可以简化演变过程。

本节介绍如何将泛型添加到现有代码;它考虑了一个小例子,一个用于扩展集合框架的堆栈库以及一个关联的客户端。我们从传统堆栈库和客户端(在泛型之前为Java编写)开始,然后展示相应的通用库和客户端(为泛型编写的 `Java`)。我们的示例代码很小,因此很容易一次性更新为泛型,但实际上库和客户端会更大,我们可能需要分别进行演变。这是原始类型的帮助,它们是参数化类型的传统对应物。
本节介绍如何将泛型添加到现有代码;它考虑了一个小例子,一个用于扩展集合框架的堆栈库以及一个关联的客户端。我们从传统堆栈库和客户端(在泛型之前为 `Java` 编
写)开始,然后展示相应的通用库和客户端(为泛型编写的 `Java`)。我们的示例代码很小,因此很容易一次性更新为泛型,但实际上库和客户端会更大,我们可能需要分
别进行演变。这是原始类型的帮助,它们是参数化类型的传统对应物。

程序的各个部分可以按照任意顺序进行演变。你可能有一个遗留客户端的通用库;对于那些使用 `Java 5`中的集合框架和遗留代码的人来说,这是常见的情况。或者你可能有一个通用客户端的遗留库;这种情况下,您希望为库提供通用签名而不需要重写整个库。我们考虑三种方法来做到这一点:对源代码,存根文件和包装器进行最小限度的更改。当你有权访问源代码时,第一个是有用的,第二个是不用的;我们建议不要第三个。
程序的各个部分可以按照任意顺序进行演变。你可能有一个遗留客户端的通用库;对于那些使用 `Java 5` 中的集合框架和遗留代码的人来说,这是常见的情况。或者你可能
有一个通用客户端的遗留库;这种情况下,您希望为库提供通用签名而不需要重写整个库。我们考虑三种方法来做到这一点:对源代码,存根文件和包装器进行最小限度的更
改。当你有权访问源代码时,第一个是有用的,第二个是不用的;我们建议不要第三个。

在实践中,类库和客户端可能涉及很多接口和类,甚至可能在图书馆和客户端之间没有明确的区别。但是这里讨论的相同原则仍然适用,并且可以用来独立于任何其他部分发展程序的任何部分。
在实践中,类库和客户端可能涉及很多接口和类,甚至可能在图书馆和客户端之间没有明确的区别。但是这里讨论的相同原则仍然适用,并且可以用来独立于任何其他部分发
展程序的任何部分。

《《《 [下一节](01_Legacy_Library_with_Legacy_Client.md) <br/>
《《《 [返回首页](../README.md)
14 changes: 12 additions & 2 deletions ch05/01_Legacy_Library_with_Legacy_Client.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](00_Evolution_Not_Revolution.md)

## 旧版客户端的旧版库

我们从一个简单的栈库和一个关联的客户端开始,如例 `5-1` 所示。这是为 `Java 1.4` 及其版本的集合框架编写的遗留代码。像集合框架一样,我们将库构造为接口
`Stack`(类似于 `List`),实现类 `ArrayStack`(类似于 `ArrayList`)和实用类 `Stacks`(类似于 `Collections`)。接口堆栈提供了三种方法:`empty`,
`push` 和 `pop`。实现类 `ArrayStack` 提供了一个没有参数的构造函数,并使用方法 `size`,`add` 和 `remove` 在列表上实现了方法 `empty`,`push`和
`pop`。流行的主体可以更短 - 而不是将值分配给变量,它可以直接返回 - 但是随着代码的发展,变量的类型如何变化会很有趣。实用程序类只提供一种方法,反向,它从
一个堆栈重复弹出并推向另一个堆栈。

我们从一个简单的栈库和一个关联的客户端开始,如例 `5-1`所示。这是为 `Java 1.4`及其版本的集合框架编写的遗留代码。像集合框架一样,我们将库构造为接口 `Stack`(类似于 `List`),实现类 `ArrayStack`(类似于 `ArrayList`)和实用类 `Stacks`(类似于 `Collections`)。接口堆栈提供了三种方法:空,推送和弹出。实现类 `ArrayStack` 提供了一个没有参数的构造函数,并使用方法 `size`,`add` 和 `remove` 在列表上实现了方法 `empty`,`push`和 `pop`。流行的主体可以更短 - 而不是将值分配给变量,它可以直接返回 - 但是随着代码的发展,变量的类型如何变化会很有趣。实用程序类只提供一种方法,反向,它从一个堆栈重复弹出并推向另一个堆栈。
客户端分配一个堆栈,向其中推入一些整数,然后弹出一个整数,然后将其余的余数转换为新的堆栈。由于这是 `Java 1.4`,所以整数必须在传递到 `push` 时显式装
箱,并且在由 `pop` 返回时显式解除装箱。

客户端分配一个堆栈,向其中推入一些整数,然后弹出一个整数,然后将其余的余数转换为新的堆栈。由于这是 `Java 1.4`,所以整数必须在传递到 `push` 时显式装箱,并且在由 `pop` 返回时显式解除装箱。
《《《 [下一节](02_Generic_Library_with_Generic_Client.md) <br/>
《《《 [返回首页](../README.md)
70 changes: 40 additions & 30 deletions ch05/02_Generic_Library_with_Generic_Client.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](01_Legacy_Library_with_Legacy_Client.md)

## 具有通用客户端的通用库

接下来,我们更新库和客户端以使用泛型,如例 `5-2` 中所示。 这是用于 `Java 5` 及其集合版框架的通用代码。 接口现在接受一个类型参数,变成 `Stack<E>`(类似于 `List<E>`),实现类也变为 `ArrayStack<E>`(类似于 `ArrayList<E>`),但没有添加类型参数 实用工具类 `Stacks`(类似于 `Collections`)。 `push` 和 `pop` 的签名和主体中的 `Object` 类型由类型参数 `E` 替换。 请注意,`ArrayStack` 中的构造函数不需要类型参数。 在实用程序类中,反向方法变为带有参数和 `Stack<T>` 类型结果的泛型方法。 适当的类型参数被添加到客户端,现在隐式装箱和取消装箱。
接下来,我们更新库和客户端以使用泛型,如例 `5-2` 中所示。 这是用于 `Java 5` 及其集合版框架的通用代码。 接口现在接受一个类型参数,变成
`Stack<E>`(类似于 `List<E>`),实现类也变为 `ArrayStack<E>`(类似于 `ArrayList<E>`),但没有添加类型参数 实用工具类 `Stacks`(类似于
`Collections`)。 `push` 和 `pop` 的签名和主体中的 `Object` 类型由类型参数 `E` 替换。 请注意,`ArrayStack` 中的构造函数不需要类型参数。 在实用程
序类中,反向方法变为带有参数和 `Stack<T>` 类型结果的泛型方法。 适当的类型参数被添加到客户端,现在隐式装箱和取消装箱。

简而言之,转换过程非常简单:只需添加一些类型参数,并用适当的类型变量替换 `Object` 的出现即可。 通过比较两个示例中突出显示的部分,可以发现传统版本和通用版本之间的所有差异。 泛型的实现设计为使两个版本生成基本上相同的类文件。 有些类型的辅助信息可能会有所不同,但要执行的实际字节码将是相同的。 因此,执行遗留版本和通用版本会产生相同的结果。 正如我们接下来讨论的那样,传统和普通资源产生相同类文件的事实可以简化进化过程。
简而言之,转换过程非常简单:只需添加一些类型参数,并用适当的类型变量替换 `Object` 的出现即可。 通过比较两个示例中突出显示的部分,可以发现传统版本和
通用版本之间的所有差异。 泛型的实现设计为使两个版本生成基本上相同的类文件。 有些类型的辅助信息可能会有所不同,但要执行的实际字节码将是相同的。 因此,
执行遗留版本和通用版本会产生相同的结果。 正如我们接下来讨论的那样,传统和普通资源产生相同类文件的事实可以简化进化过程。

例 `5-1`。 传统客户端的旧版库

```java
l/Stack.java:
interface Stack {
public boolean empty();
public void push(Object elt);
public Object pop();
}
interface Stack {
public boolean empty();
public void push(Object elt);
public Object pop();
}
l/ArrayStack.java:
import java.util.*;
class ArrayStack implements Stack {
private List list;
public ArrayStack() { list = new ArrayList(); }
public boolean empty() { return list.size() == 0; }
public void push(Object elt) { list.add(elt); }
public Object pop() {
Object elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
import java.util.*;
class ArrayStack implements Stack {
private List list;
public ArrayStack() { list = new ArrayList(); }
public boolean empty() { return list.size() == 0; }
public void push(Object elt) { list.add(elt); }
public Object pop() {
Object elt = list.remove(list.size()-1);
return elt;
}
public String toString() { return "stack"+list.toString(); }
}
l/Stacks.java:
class Stacks {
public static Stack reverse(Stack in) {
Stack out = new ArrayStack();
while (!in.empty()) {
Object elt = in.pop();
out.push(elt);
}
return out;
}
}
l/Client.java:
class Stacks {
public static Stack reverse(Stack in) {
Stack out = new ArrayStack();
while (!in.empty()) {
Object elt = in.pop();
out.push(elt);
}
return out;
}
}
l/Client.java:
class Client {
public static void main(String[] args) {
Stack stack = new ArrayStack();
Expand All @@ -52,3 +60,5 @@
}
```

《《《 [下一节](03_Generic_Library_with_Legacy_Client.md) <br/>
《《《 [返回首页](../README.md)
47 changes: 26 additions & 21 deletions ch05/03_Generic_Library_with_Legacy_Client.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](02_Generic_Library_with_Generic_Client.md)

## 具有传统客户端的通用库

现在让我们考虑一下这种情况,即在客户端保留其旧版本时,库更新为泛型。这可能是因为没有足够的时间一次转换所有内容,或者因为类库和客户由不同的组织控制。
Expand Down Expand Up @@ -28,15 +31,15 @@

```java
g/Stack.java:
interface Stack<E> {
public boolean empty();
public void push(E elt);
public E pop();
}
interface Stack<E> {
public boolean empty();
public void push(E elt);
public E pop();
}

g/ArrayStack.java:
import java.util.*;
class ArrayStack<E> implements Stack<E> {
import java.util.*;
class ArrayStack<E> implements Stack<E> {
private List<E> list;
public ArrayStack() { list = new ArrayList<E>(); }
public boolean empty() { return list.size() == 0; }
Expand All @@ -48,7 +51,7 @@
public String toString() { return "stack"+list.toString(); }
}

g/Stacks.java:
g/Stacks.java:
class Stacks {
public static <T> Stack<T> reverse(Stack<T> in) {
Stack<T> out = new ArrayStack<T>();
Expand All @@ -60,19 +63,19 @@
}
}

g/Client.java:
class Client {
public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<Integer>();
for (int i = 0; i<4; i++) stack.push(i);
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = stack.pop();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack<Integer> reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
g/Client.java:
class Client {
public static void main(String[] args) {
Stack<Integer> stack = new ArrayStack<Integer>();
for (int i = 0; i<4; i++) stack.push(i);
assert stack.toString().equals("stack[0, 1, 2, 3]");
int top = stack.pop();
assert top == 3 && stack.toString().equals("stack[0, 1, 2]");
Stack<Integer> reverse = Stacks.reverse(stack);
assert stack.empty();
assert reverse.toString().equals("stack[2, 1, 0]");
}
}
```

如果我们遵循上面的建议,并在启用适当的开关的情况下重新运行编译器,我们会得到更多的细节:
Expand Down Expand Up @@ -130,3 +133,5 @@
这编译了遗留代码并且没有发出警告或错误。 这种关闭警告的方法只适用于真正的遗留代码,没有 `Java 5` 中引入的通用功能或其他功能。 也可以使用注释关闭未经
检查的警告,如下一节所述,即使使用 `Java 5` 中引入的功能,也可以使用这些警告。

《《《 [下一节](04_Legacy_Library_with_Generic_Client.md) <br/>
《《《 [返回首页](../README.md)
5 changes: 5 additions & 0 deletions ch05/04_Legacy_Library_with_Generic_Client.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
《《《 [返回首页](../README.md) <br/>
《《《 [上一节](03_Generic_Library_with_Legacy_Client.md)

## 具有通用客户端的旧版库

在客户端之前更新库通常是有意义的,但可能会出现您希望以其他方式进行更新的情况。 例如,您可能负责维护客户而不是类库; 或者类库可能很大,因此您可能需要逐
Expand Down Expand Up @@ -236,3 +239,5 @@
通过确保遗留对象和泛型对象相同,`Java` 泛型的设计避免了包装器的所有这些问题。 `C#` 泛型的设计有很大的不同:遗留类和泛型类是完全不同的,任何结合遗留
集合和泛型集合的尝试都会碰到这里讨论的包装器的困难。

《《《 [下一节](05_Conclusions.md) <br/>
《《《 [返回首页](../README.md)
Loading