diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index e10f8751..cf27c665 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -690,9 +690,9 @@ doh(Milhouse) ```` -**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。正如你将在下一章中看到的那样,比起重载,更常见的是覆盖同名方法,使用与基类中完全相同的方法签名[^1]和返回类型。否则会让人感到困惑。 +**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。正如你将在下一章中看到的那样,比起重载,更常见的是覆盖同名方法,使用与基类中完全相同的方法签名[^2]和返回类型。否则会让人感到困惑。 -你已经看到了Java 5 **@Override**注解,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法[^2]时,你可以选择添加这个注解,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: +你已经看到了Java 5 **@Override**注解,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法[^3]时,你可以选择添加这个注解,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: ```java @@ -712,12 +712,7 @@ class Lisa extends Homer { **{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:method does not override a method from its superclass.方法不会重写超类中的方法, **@Override** 注解能防止你意外地重载。 -- **[1]** 方法签名——方法名和参数类型的合称 - -- **[2]** 重写——覆盖同名方法,使用与基类中完全相同的方法签名和返回类型[^3] - -- **[3]** 在java 1.4版本以前,重写方法的返回值类型被要求必须与被重写方法一致,但是在java 5.0中放宽了这一个限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类。 - + ## 组合与继承的选择 @@ -1182,8 +1177,6 @@ Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vect Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 -- **[1]** Java 1.4 开始已将 **Vector** 类大多数方法的 **final** 去掉 - ## 类初始化和加载 @@ -1267,6 +1260,15 @@ j = 39 当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。 +[^1]: Java 1.4 开始已将 **Vector** 类大多数方法的 **final** 去掉 + +[^2]: 方法签名——方法名和参数类型的合称 + +[^3]: 重写——覆盖同名方法,使用与基类中完全相同的方法签名和返回类型[^4] + +[^4]: 在java 1.4版本以前,重写方法的返回值类型被要求必须与被重写方法一致,但是在java 5.0中放宽了这一个限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类。 + +
diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 83376eb8..caf557e8 100755 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1434,7 +1434,7 @@ LocalInnerClass.class 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况[^1]。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) -- **[1]** 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 +[^1]: 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 ## 本章小结 diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 269fc785..131d1771 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -17,12 +17,12 @@ MyType aReference; 集合还有一些其它特性。例如, **Set** 对于每个值都只保存一个对象, **Map** 是一个关联数组,允许将某些对象与其他对象关联起来。Java集合类都可以自动地调整自己的大小。因此,与数组不同,在编程时,可以将任意数量的对象放置在集合中,而不用关心集合应该有多大。 -尽管在 Java 中没有直接的关键字支持,[^1]但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。 +尽管在 Java 中没有直接的关键字支持[^1],但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。 ## 泛型和类型安全的集合 -使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 `add()` 插入对象;然后用 `get()` 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 `size()` 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。 +使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 `add()` 插入对象;然后用 `get()` 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号[^2]。 **ArrayList** 还有一个 `size()` 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。 在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 `@SuppressWarning` 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息): @@ -70,7 +70,7 @@ ndOrangesWithoutGenerics.java:23) 在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。 -通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型: +通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中[^3]。下面还是这个示例,但是使用了泛型: ```java // collections/ApplesAndOrangesWithGenerics.java import java.util.*; @@ -310,7 +310,7 @@ public class PrintingCollections { */ ``` -这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。 +这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键* (key)和与之关联的*值* (value)。 默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。 @@ -318,7 +318,7 @@ public class PrintingCollections { **ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。 -**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。 +**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它把对象按照比较规则来排序;还有 **LinkedHashSet** ,它把对象按照被添加的先后顺序来排序。 **Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。 @@ -328,7 +328,7 @@ public class PrintingCollections { 本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。 -键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。 +键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 把所有的键按照比较规则来排序, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按照键的插入顺序来排序。 @@ -729,7 +729,7 @@ pets.removeLast(): Hamster 堆栈是“后进先出”(LIFO)集合。它有时被称为*叠加栈*(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。 -Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们永远坚持 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法: +Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们被迫一直忍受 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法: ```java // collections/StackTest.java @@ -861,7 +861,7 @@ public class SetOfInteger { 在 0 到 29 之间的 10000 个随机整数被添加到 **Set** 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。 -早期 Java 版本中的 **HashSet** 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 **Integer** 按顺序排序。但是,您不应该依赖此行为: +早期 Java 版本中的 **HashSet** 产生的输出没有明显的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 也使用散列来提高查询速度,但是似乎使用了链表来维护元素的插入顺序。显然,散列算法有改动,以至于现在(上述示例中的HashSet ) **Integer** 是有序的。但是,您不应该依赖此行为(下面例子就没有排序): ```java // collections/SetOfString.java @@ -990,7 +990,7 @@ void] */ ``` -我们逐步浏览文件中的每一行,并使用 `String.split()` 将其分解为单词,这里使用正则表达式 **\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写和小写字母位于不同的组中。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象): +我们逐步浏览文件中的每一行,并使用 `String.split()` 将其分解为单词,这里使用正则表达式 **\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写字母和小写字母是分开的。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象): ```java // collections/UniqueWordsAlphabetic.java @@ -1088,7 +1088,7 @@ true */ ``` -**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\>** 即可: +**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度:只需要创建一个 **Map** ,其值也是 **Map** (这些 **Map** 的值可以是其他集合,甚至是其他的 **Map** )。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\>** 即可: ```java @@ -1164,7 +1164,7 @@ Person Luke has: 队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在[并发编程]()中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。 -**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中与 **Queue** 相关(Queue-specific)的方法: +**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中的 **Queue** 特有(Queue-specific)方法: ```java // collections/QueueDemo.java @@ -1195,18 +1195,18 @@ B r o n t o s a u r u s */ ``` -`offer()` 是与 **Queue** 相关的方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 `peek()` 和 `element()` 都返回队头元素而不删除它,但是如果队列为空,则 `element()` 抛出 **NoSuchElementException** ,而 `peek()` 返回 **null** 。 `poll()` 和 `remove()`* 都删除并返回队头元素,但如果队列为空,`poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。 +`offer()` 是 **Queue** 的特有方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 `peek()` 和 `element()` 都返回队头元素而不删除它,但如果队列为空,则 `peek()` 返回 **null** , 而 `element()` 抛出 **NoSuchElementException** 。 `poll()` 和 `remove()` 都删除并返回队头元素,但如果队列为空,则 `poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。 自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。 -与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。 +**Queue** 的特有方法提供了独立而完整的功能。 换句话说, **Queue** 无需调用继承自 **Collection** 的方法,(只依靠 **Queue** 的特有方法)就有队列的功能。 ### 优先级队列PriorityQueue -先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个弹出的元素应该是等待时间最长的元素。 +先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出:下一个弹出的元素应该是等待时间最长的那一个。 -优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。 +*优先级队列* :下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,飞机即将起飞的、尚未登机的乘客可能会被拉出队伍(作最优先的处理)。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们到达时间先后。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。 当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。 @@ -1274,9 +1274,9 @@ C B A A ## 集合与迭代器 -**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。 +**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,你可以创建 **AbstractCollection** 的子类型来避免不必要的代码重复。 -使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 `iterator()` 方法: +使用接口的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 `iterator()` 方法: ```java // collections/InterfaceVsIterator.java @@ -1337,7 +1337,7 @@ Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx} 在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 `display(Collection)` 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。 -当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,即使我们不在 `display()` 方法中使用它们,也必须这样做。虽然这可以通过继承 **AbstractCollection** 而很容易地实现,但是无论如何还是要被强制去实现 `iterator()` 和 `size()` 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到: +当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,纵使我们不会在 `display()` 方法中使用它们,也要这样做。尽管通过继承 **AbstractCollection** 会容易些,但是 **AbstractCollection** 还有 `iterator()` 和 `size()`没有实现(抽象方法),而 **AbstractCollection** 中的其它方法会用到它们,因此必须以自己的方式实现这两个方法: ```java // collections/CollectionSequence.java @@ -1383,7 +1383,7 @@ extends AbstractCollection { - **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。 -这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能再继承 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多: +这个例子表明,如果实现了 **Collection** ,就必须也实现 `iterator()` ,而单独只实现 `iterator()` 和继承 **AbstractCollection** 相比,并没有容易多少。但是,如果类已经继承了其他的类,那么就没法再继承 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力(单独只实现 `iterator()`)要容易得多: ```java // collections/NonCollectionSequence.java @@ -1422,12 +1422,12 @@ public class NonCollectionSequence extends PetSequence { */ ``` -生成 **Iterator** 是将序列与消费该序列的方法连接在一起耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。 +生成 **Iterator** 是将序列与消费该序列的方法连接在一起的耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。 ## for-in和迭代器 -到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是一个更通用的证明: +到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是它的通用性的证明: ```java // collections/ForInCollections.java @@ -1508,7 +1508,7 @@ public class EnvironmentVariables { `System.getenv()` [^7]返回一个 **Map** , `entrySet()` 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。 -*for-in* 语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱: +*for-in* 语句适用于数组或者其它任何 **Iterable** ,但这并不代表数组一定是 **Iterable** ,也不会发生任何自动装箱: ```java // collections/ArrayIsNotIterable.java @@ -1684,7 +1684,7 @@ array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] */ ``` -在第一种情况下, `Arrays.asList()` 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 `Arrays.asList(ia)` 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 `Arrays.asList()` 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果执行的操作会修改这个 **List** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。 +在第一种情况下, `Arrays.asList()` 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 `Arrays.asList(ia)` 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 `Arrays.asList()` 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果对 **List** 对象做了任何修改,又不想让原始数组被修改,那么就应该在另一个集合中创建一个副本。 ## 本章小结 @@ -1804,11 +1804,11 @@ Serializable] [^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。 -[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。 +[^4]: `remove()` 是一个所谓的“可选”方法(还有其它这样的方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。 -[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。 +[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果一个对象在队列中等待时,它的优先级会发生变化,那么算法的选择就很重要。 -[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。 +[^6]: 有些人鼓吹不加思索地创建接口对应一个类甚至每个类中的所有可能的方法组合。 但我相信接口的意义不应该仅限于机械地重复方法组合,因此我倾向于看到增加接口的价值才创建它。 [^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。