From f859392b75da2c9c49a377b370af91e133a3c26b Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Mon, 29 Jul 2019 10:32:57 +0800
Subject: [PATCH 001/475] fix 05 typo
---
docs/book/05-Control-Flow.md | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md
index f9674a64..4e666ac3 100644
--- a/docs/book/05-Control-Flow.md
+++ b/docs/book/05-Control-Flow.md
@@ -7,7 +7,7 @@
Java 使用了 C 的所有执行控制语句,因此对于熟悉 C/C++ 编程的人来说,这部分内容轻车熟路。大多数面向过程编程语言都有共通的某种控制语句。在 Java 中,涉及的关键字包括 **if-else,while,do-while,for,return,break** 和选择语句 **switch**。 Java 并不支持备受诟病的 **goto**(尽管它在某些特殊场景中依然是最行之有效的方法)。 尽管如此,在 Java 中我们仍旧可以进行类似的逻辑跳转,但较之典型的 **goto** 用法限制更多。
-## true和flase
+## true 和 false
所有的条件语句都利用条件表达式的“真”或“假”来决定执行路径。举例:
`a == b`。它利用了条件表达式 `==` 来比较 `a` 与 `b` 的值是否相等。 该表达式返回 `true` 或 `false`。代码示例:
@@ -235,7 +235,7 @@ i = 4 j = 8
上例中 **int** 类型声明包含了 `i` 和 `j`。实际上,在初始化部分我们可以定义任意数量的同类型变量。**注意**:在 Java 中,仅允许 **for** 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。
-## for-in语法
+## for-in 语法
Java 5 引入了更为简洁的“增强版 **for** 循环”语法来操纵数组和集合。(更多细节,可参考 [数组](./21-Arrays.md) 和 [集合](./12-Collections.md) 章节内容)。大部分文档也称其为 **for-each** 语法,但因为了不与 Java 8 新添的 `forEach()` 产生混淆,因此我称之为 **for-in** 循环。 (Python 已有类似的先例,如:**for x in sequence**)。**注意**:你可能会在其他地方看到不同叫法。
@@ -355,7 +355,7 @@ public class ForInInt {
在 Java 中有几个关键字代表无条件分支,这意味无需任何测试即可发生。这些关键字包括 **return**,**break**,**continue** 和跳转到带标签语句的方法,类似于其他语言中的 **goto**。
-**return** 关键字有两方面的作用:1.指定一个方法返回值(在方法返回类型非 **void** 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 `return` 的这些特点来改写上例 `IfElse.java` 文件中的 `test()` 方法。代码示例:
+**return** 关键字有两方面的作用:1.指定一个方法返回值 (在方法返回类型非 **void** 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 `return` 的这些特点来改写上例 `IfElse.java` 文件中的 `test()` 方法。代码示例:
```java
// control/TestWithReturn.java
@@ -389,7 +389,7 @@ public class TestWithReturn {
如果在方法签名中定义了返回值类型为 **void**,那么在代码执行结束时会有一个隐式的 **return**。 也就是说我们不用在总是在方法中显式地包含 **return** 语句。 **注意**:如果你的方法声明的返回值类型为非 **void** 类型,那么则必须确保每个代码路径都返回一个值。
-## break和continue
+## break 和 continue
在任何迭代语句的主体内,都可以使用 **break** 和 **continue** 来控制循环的流程。 其中,**break** 表示跳出当前循环体。而 **continue** 表示停止本次循环,开始下一次循环。
@@ -442,7 +442,7 @@ public class BreakAndContinue {
**[3]** 无限 **while** 循环。循环内的 **break** 语句可中止循环。**注意**,**continue** 语句可将控制权移回循环的顶部,而不会执行 **continue** 之后的任何操作。 因此,只有当 `i` 的值可被 10 整除时才会输出。在输出中,显示值 0,因为 `0%9` 产生 0。还有一种无限循环的形式: `for(;;)`。 在编译器看来,它与 `while(true)` 无异,使用哪种完全取决于你的编程品味。
-## 臭名昭著的goto
+## 臭名昭著的 goto
[**goto** 关键字](https://en.wikipedia.org/wiki/Goto) 很早就在程序设计语言中出现。事实上,**goto** 起源于[汇编](https://en.wikipedia.org/wiki/Assembly_language)(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。
@@ -485,7 +485,7 @@ outer-iteration {
```java
// control/LabeledFor.java
-// 搭配“标签break”的for循环中使用 break 和 continue
+// 搭配“标签 break”的 for 循环中使用 break 和 continue
public class LabeledFor {
public static void main(String[] args) {
@@ -523,7 +523,7 @@ public class LabeledFor {
}
}
}
- // 在此处无法break或continue标签
+ // 在此处无法 break 或 continue 标签
}
}
```
@@ -624,7 +624,7 @@ break outer
大家要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 **break** 或 **continue**。
-**break** 和 **continue** 标签在编码中的使用频率相对较低(此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。
+**break** 和 **continue** 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。
在 *Dijkstra* 的 **“Goto 有害”** 论文中,他最反对的就是标签,而非 **goto**。他观察到 BUG 的数量似乎随着程序中标签的数量而增加[^2]。标签和 **goto** 使得程序难以分析。但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制,无法用于以临时方式传输控制。由此也引出了一个有趣的情形:对语言能力的限制,反而使它这项特性更加有价值。
@@ -649,7 +649,7 @@ switch(integral-selector) {
在上面的定义中,大家会注意到每个 **case** 均以一个 **break** 结尾。这样可使执行流程跳转至 **switch** 主体的末尾。这是构建 **switch** 语句的一种传统方式,但 **break** 是可选的。若省略 **break,** 会继续执行后面的 **case** 语句的代码,直到遇到一个 **break** 为止。通常我们不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的 **default** 语句没有 **break**,因为执行流程已到了 **break** 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在 **default** 语句的末尾放置一个 **break**,尽管它并没有任何实际的作用。
-**switch** 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 **int** 或 **char** 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 **if** 语句。 在[下一章的结尾](./06-Housekeeping.md#枚举类型)中,我们将会了解到**枚举类型**被用来搭配 **switch** 工作,并优雅地解决了这种限制。
+**switch** 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 **int** 或 **char** 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 **if** 语句。 在[下一章的结尾](./06-Housekeeping.md#枚举类型) 中,我们将会了解到**枚举类型**被用来搭配 **switch** 工作,并优雅地解决了这种限制。
下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:
@@ -713,7 +713,7 @@ int c = rand.nextInt(26) + 'a';
-## switch字符串
+## switch 字符串
Java 7 增加了在字符串上 **switch** 的用法。 下例展示了从一组 **String** 中选择可能值的传统方法,以及新式方法:
@@ -820,8 +820,8 @@ java RandomBounds upper
[^1]: 在早期的语言中,许多决策都是基于让编译器设计者的体验更好。 但在现代语言设计中,许多决策都是为了提高语言使用者的体验,尽管有时会有妥协 —— 这通常会让语言设计者后悔。
-[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误](https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation)的例子。
+[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误(https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation) 的例子。
-
\ No newline at end of file
+
From 84e5f371279049cf89c8a339e41708eb87b5aa98 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Mon, 29 Jul 2019 10:35:17 +0800
Subject: [PATCH 002/475] Update 05-Control-Flow.md
---
docs/book/05-Control-Flow.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md
index 4e666ac3..e2f0dcb3 100644
--- a/docs/book/05-Control-Flow.md
+++ b/docs/book/05-Control-Flow.md
@@ -818,9 +818,9 @@ java RandomBounds upper
本章总结了我们对大多数编程语言中出现的基本特性的探索:计算,运算符优先级,类型转换,选择和迭代。 现在让我们准备好,开始步入面向对象和函数式编程的世界吧。 下一章的内容涵盖了 Java 编程中的重要问题:对象的[初始化和清理](./06-Housekeeping.md)。紧接着,还会介绍[封装](./07-Implementation-Hiding.md)(implementation hiding)的核心概念。
-[^1]: 在早期的语言中,许多决策都是基于让编译器设计者的体验更好。 但在现代语言设计中,许多决策都是为了提高语言使用者的体验,尽管有时会有妥协 —— 这通常会让语言设计者后悔。
+[^1]: 在早期的语言中,许多决策都是基于让编译器设计者的体验更好。 但在现代语言设计中,许多决策都是为了提高语言使用者的体验,尽管有时会有妥协 —— 这通常会让语言设计者后悔。
-[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误(https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation) 的例子。
+[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误](https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation) 的例子。
From a01e8e9ca68e74ea91a714f957616a6587f8f85e Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Mon, 29 Jul 2019 19:36:34 +0800
Subject: [PATCH 003/475] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E9=9B=86?=
=?UTF-8?q?=E5=90=88=E4=B8=BB=E9=A2=98=20=E7=BF=BB=E8=AF=91=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E8=87=B3=20=E7=90=86=E8=A7=A3Map?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Collection-Topics.md | 274 +++++++++++++++++++++++-
1 file changed, 273 insertions(+), 1 deletion(-)
diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md
index 15dc2b01..becdfba2 100644
--- a/docs/book/Appendix-Collection-Topics.md
+++ b/docs/book/Appendix-Collection-Topics.md
@@ -1857,7 +1857,7 @@ List.set(): java.lang.UnsupportedOperationException
[第十二章 集合]()章节中的 **Set** 有关示例对 **Set** 的基本操作做了很好的介绍。 但是,这些示例可以方便地使用预定义的 Java 类型,例如 **Integer** 和 **String** ,它们可以在集合中使用。在创建自己的类型时请注意, **Set** (以及稍后会看到的 **Map** )需要一种维护存储顺序的方法,该顺序因 **Set** 的不同实现而异。因此,不同的 **Set** 实现不仅具有不同的行为,而且它们对可以放入特定 **Set** 中的对象类型也有不同的要求:
-| **Set** 类型 | 要求 |
+| **Set** 类型 | 约束 |
| :---: | :--- |
| **Set(interface)** | 添加到 **Set** 中的每个元素必须是唯一的,否则,**Set** 不会添加重复元素。添加到 **Set** 的元素必须至少定义 `equals()` 方法以建立对象唯一性。 **Set** 与 **Collection** 具有完全相同的接口。 **Set** 接口不保证它将以任何特定顺序维护其元素。 |
| **HashSet\*** | 注重快速查找元素的集合,其中元素必须定义 `hashCode()` 和 `equals()` 方法。 |
@@ -2237,6 +2237,277 @@ ConcurrentLinkedDeque
## 理解Map
+正如在[第十二章 集合]()章节中所了解到的,**Map**(也称为 *关联数组* )维护键值关联(对),因此可以使用键来查找值。标准 Java 库包含不同的 **Map** 基本实现,例如 **HashMap** , **TreeMap** , **LinkedHashMap** , **WeakHashMap** , **ConcurrentHashMap** 和 **IdentityHashMap** 。 它们都具有相同的基本 **Map** 接口,但它们的行为不同,包括效率,键值对的保存顺序和呈现顺序,保存对象的时间,如何在多线程程序中工作,以及如何确定键的相等性。 **Map** 接口的实现数量应该告诉你一些关于此工具重要性的信息。
+
+为了更深入地了解 **Map** ,学习如何构造关联数组会很有帮助。下面是一个非常简单的实现:
+
+```java
+// collectiontopics/AssociativeArray.java
+// Associates keys with values
+
+public class AssociativeArray {
+ private Object[][] pairs;
+ private int index;
+ public AssociativeArray(int length) {
+ pairs = new Object[length][2];
+ }
+ public void put(K key, V value) {
+ if(index >= pairs.length)
+ throw new ArrayIndexOutOfBoundsException();
+ pairs[index++] = new Object[]{ key, value };
+ }
+ @SuppressWarnings("unchecked")
+ public V get(K key) {
+ for(int i = 0; i < index; i++)
+ if(key.equals(pairs[i][0]))
+ return (V)pairs[i][1];
+ return null; // Did not find key
+ }
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ for(int i = 0; i < index; i++) {
+ result.append(pairs[i][0].toString());
+ result.append(" : ");
+ result.append(pairs[i][1].toString());
+ if(i < index - 1)
+ result.append("\n");
+ }
+ return result.toString();
+ }
+ public static void main(String[] args) {
+ AssociativeArray map =
+ new AssociativeArray<>(6);
+ map.put("sky", "blue");
+ map.put("grass", "green");
+ map.put("ocean", "dancing");
+ map.put("tree", "tall");
+ map.put("earth", "brown");
+ map.put("sun", "warm");
+ try {
+ map.put("extra", "object"); // Past the end
+ } catch(ArrayIndexOutOfBoundsException e) {
+ System.out.println("Too many objects!");
+ }
+ System.out.println(map);
+ System.out.println(map.get("ocean"));
+ }
+}
+/* Output:
+Too many objects!
+sky : blue
+grass : green
+ocean : dancing
+tree : tall
+earth : brown
+sun : warm
+dancing
+*/
+```
+
+关联数组中的基本方法是 `put()` 和 `get()` ,但为了便于显示,重写了 `toString()` 方法以打印键值对。为了显示它的工作原理,主方法加载一个带有字符串对的 **AssociativeArray** 并打印生成的映射,然后调用其中一个值的 `get()` 方法。
+
+要使用 `get()` 方法,可以传入要查找的 **key** ,它将生成相关联的值作为结果,如果找不到则返回 **null** 。 `get()` 方法使用可能是效率最低的方法来定位值:从数组的头部开始并使用 `equals()` 来比较键。但这里是侧重于简单,而不是效率。
+
+这个版本很有启发性,但它不是很有效,而且它只有一个固定的大小,这是不灵活的。幸运的是, **java.util** 中的那些 **Map** 没有这些问题。
+
+
+### 性能
+
+性能是 **Map** 的基本问题,在 `get()` 中使用线性方法搜索一个键时会非常慢。这就是 **HashMap** 要加速的地方。它使用一个称为 *哈希码* 的特殊值来替代慢速搜索一个键。哈希码是一种从相关对象中获取一些信息并将其转换为该对象的“相对唯一” **int** 的方法。 `hashCode()` 是根类 **Object** 中的一个方法,因此所有 Java 对象都可以生成哈希码。 **HashMap** 获取对象的 `hashCode()` 并使用它来快速搜索键。这就使得性能有了显著的提升。[^3]
+
+以下是基本的 **Map** 实现。 **HashMap**上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。其他实现强调其他特性,因此不如 **HashMap** 快。
+
+| **Map** 实现 | 描述 |
+| :---: | :--- |
+| **HashMap\*** | 基于哈希表的实现。(使用此类来代替 **Hashtable** 。)为插入和定位键值对提供了常数时间性能。可以通过构造方法调整性能,这些构造方法允许你设置哈希表的容量和装填因子。 |
+| **LinkedHashMap** | 与 **HashMap** 类似,但是当遍历时,可以按插入顺序或最近最少使用(LRU)顺序获取键值对。只比 **HashMap** 略慢,一个例外是在迭代时,由于其使用链表维护内部顺序,所以会更快些。 |
+| **TreeMap** | 基于红黑树的实现。当查看键或键值对时,它们按排序顺序(由 **Comparable** 或 **Comparator** 确定)。 **TreeMap** 的侧重点是按排序顺序获得结果。 **TreeMap** 是唯一使用 `subMap()` 方法的 **Map** ,它返回红黑树的一部分。 |
+| **WeakHashMap** | 一种具有 *弱键*(weak keys) 的 **Map** ,为了解决某些类型的问题,它允许释放 **Map** 所引用的对象。如果在 **Map** 外没有对特定键的引用,则可以对该键进行垃圾回收。 |
+| **ConcurrentHashMap** | 不使用同步锁定的线程安全 **Mao** 。这在[第二十四章 并发编程]() 一章中讨论。 |
+| **IdentityHashMap** | 使用 `==` 而不是 `equals()` 来比较键。仅用于解决特殊问题,不适用于一般用途。 |
+
+散列是在 **Map** 中存储元素的最常用方法。
+
+**Map** 中使用的键的要求与 **Set** 中的元素的要求相同。可以在 **TypesForSets.java** 中看到这些。任何键必须具有 `equals()` 方法。如果键用于散列映射,则它还必须具有正确的 `hashCode()` 方法。如果键在 **TreeMap** 中使用,则必须实现 **Comparable** 接口。
+
+以下示例使用先前定义的 **CountMap** 测试数据集显示通过 **Map** 接口可用的操作:
+
+```java
+// collectiontopics/MapOps.java
+// Things you can do with Maps
+import java.util.concurrent.*;
+import java.util.*;
+import onjava.*;
+
+public class MapOps {
+ public static
+ void printKeys(Map map) {
+ System.out.print("Size = " + map.size() + ", ");
+ System.out.print("Keys: ");
+ // Produce a Set of the keys:
+ System.out.println(map.keySet());
+ }
+ public static
+ void test(Map map) {
+ System.out.println(
+ map.getClass().getSimpleName());
+ map.putAll(new CountMap(25));
+ // Map has 'Set' behavior for keys:
+ map.putAll(new CountMap(25));
+ printKeys(map);
+ // Producing a Collection of the values:
+ System.out.print("Values: ");
+ System.out.println(map.values());
+ System.out.println(map);
+ System.out.println("map.containsKey(11): " +
+ map.containsKey(11));
+ System.out.println(
+ "map.get(11): " + map.get(11));
+ System.out.println("map.containsValue(\"F0\"): "
+ + map.containsValue("F0"));
+ Integer key = map.keySet().iterator().next();
+ System.out.println("First key in map: " + key);
+ map.remove(key);
+ printKeys(map);
+ map.clear();
+ System.out.println(
+ "map.isEmpty(): " + map.isEmpty());
+ map.putAll(new CountMap(25));
+ // Operations on the Set change the Map:
+ map.keySet().removeAll(map.keySet());
+ System.out.println(
+ "map.isEmpty(): " + map.isEmpty());
+ }
+ public static void main(String[] args) {
+ test(new HashMap<>());
+ test(new TreeMap<>());
+ test(new LinkedHashMap<>());
+ test(new IdentityHashMap<>());
+ test(new ConcurrentHashMap<>());
+ test(new WeakHashMap<>());
+ }
+}
+/* Output: (First 11 Lines)
+HashMap
+Size = 25, Keys: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
+Values: [A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0,
+L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0]
+{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,
+9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0,
+17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0}
+map.containsKey(11): true
+map.get(11): L0
+map.containsValue("F0"): true
+First key in map: 0
+Size = 24, Keys: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
+map.isEmpty(): true
+map.isEmpty(): true
+ ...
+*/
+```
+
+`printKeys()` 方法演示了如何生成 **Map** 的 **Collection** 视图。 `keySet()` 方法生成一个由 **Map** 中的键组成的 **Set** 。 打印 `values()` 方法的结果会生成一个包含 **Map** 中所有值的 **Collection** 。(请注意,键必须是唯一的,但值可以包含重复项。)由于这些 **Collection** 由 **Map** 支持,因此 **Collection** 中的任何更改都会反映在所关联的 **Map** 中。
+
+程序的其余部分提供了每个 **Map** 操作的简单示例,并测试了每种基本类型的 **Map** 。
+
+
+### SortedMap
+
+使用 **SortedMap** (由 **TreeMap** 或 **ConcurrentSkipListMap** 实现),键保证按排序顺序,这允许在 **SortedMap** 接口中使用这些方法来提供其他功能:
+
+- `Comparator comparator()` :生成用于此 **Map** 的比较器, **null** 表示自然排序。
+- `T firstKey()` :返回第一个键。
+- `T lastKey()` :返回最后一个键。
+- `SortedMap subMap(fromKey,toKey)` :生成此 **Map** 的视图,其中键从 **fromKey**(包括),到 **toKey** (不包括)。
+- `SortedMap headMap(toKey)` :使用小于 **toKey** 的键生成此 **Map** 的视图。
+- `SortedMap tailMap(fromKey)` :使用大于或等于 **fromKey** 的键生成此 **Map** 的视图。
+
+这是一个类似于 **SortedSetDemo.java** 的示例,显示了 **TreeMap** 的这种额外行为:
+
+```java
+// collectiontopics/SortedMapDemo.java
+// What you can do with a TreeMap
+import java.util.*;
+import onjava.*;
+
+public class SortedMapDemo {
+ public static void main(String[] args) {
+ TreeMap sortedMap =
+ new TreeMap<>(new CountMap(10));
+ System.out.println(sortedMap);
+ Integer low = sortedMap.firstKey();
+ Integer high = sortedMap.lastKey();
+ System.out.println(low);
+ System.out.println(high);
+ Iterator it =
+ sortedMap.keySet().iterator();
+ for(int i = 0; i <= 6; i++) {
+ if(i == 3) low = it.next();
+ if(i == 6) high = it.next();
+ else it.next();
+ }
+ System.out.println(low);
+ System.out.println(high);
+ System.out.println(sortedMap.subMap(low, high));
+ System.out.println(sortedMap.headMap(high));
+ System.out.println(sortedMap.tailMap(low));
+ }
+}
+/* Output:
+{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,
+9=J0}
+0
+9
+3
+7
+{3=D0, 4=E0, 5=F0, 6=G0}
+{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0}
+{3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0}
+*/
+```
+
+这里,键值对按照键的排序顺序进行排序。因为 **TreeMap** 中存在顺序感,所以“位置”的概念很有意义,因此可以拥有第一个、最后一个元素或子图。
+
+
+### LinkedHashMap
+
+**LinkedHashMap** 针对速度进行哈希处理,但在遍历期间也会按插入顺序生成键值对( `System.out.println()` 可以遍历它,因此可以看到遍历的结果)。 此外,可以在构造方法中配置 **LinkedHashMap** 以使用基于访问的 *最近最少使用*(LRU) 算法,因此未访问的元素(因此是删除的候选者)会出现在列表的前面。 这样可以轻松创建一个能够定期清理以节省空间的程序。下面是一个显示这两个功能的简单示例:
+
+```java
+// collectiontopics/LinkedHashMapDemo.java
+// What you can do with a LinkedHashMap
+import java.util.*;
+import onjava.*;
+
+public class LinkedHashMapDemo {
+ public static void main(String[] args) {
+ LinkedHashMap linkedMap =
+ new LinkedHashMap<>(new CountMap(9));
+ System.out.println(linkedMap);
+ // Least-recently-used order:
+ linkedMap =
+ new LinkedHashMap<>(16, 0.75f, true);
+ linkedMap.putAll(new CountMap(9));
+ System.out.println(linkedMap);
+ for(int i = 0; i < 6; i++)
+ linkedMap.get(i);
+ System.out.println(linkedMap);
+ linkedMap.get(0);
+ System.out.println(linkedMap);
+ }
+}
+/* Output:
+{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}
+{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}
+{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}
+{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}
+*/
+```
+
+这些键值对确实是按照插入顺序进行遍历,即使对于LRU版本也是如此。 但是,在LRU版本中访问前六项(仅限)后,最后三项将移至列表的前面。然后,当再次访问“ **0** ”后,它移动到了列表的后面。
## 集合工具类
@@ -2261,6 +2532,7 @@ ConcurrentLinkedDeque
[^2]: 虽然当我用这种方式描述它的时候听起来很奇怪而且好像没什么用处,但在[第十九章 类型信息]()章节中已经看到过,这种动态行为也可以非常强大有用。
+[^3]: 如果这些加速仍然无法满足性能需求,则可以通过编写自己的 **Map** 并将其自定义为特定类型来进一步加速表查找,以避免因向 **对象** 转换而导致的延迟。为了达到更高的性能水平,速度爱好者可以使用 Donald Knuth 的《计算机程序设计艺术(第3卷):排序与查找》(第二版),将溢出桶列表(overflow bucket lists)替换为具有两个额外优势的阵列:它们可以针对磁盘存储进行优化,并且它们可以节省大部分创建和回收个别记录(individual records)的时间。
From 913e125d902d0dff3e37e3058212737220b6f3fa Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Tue, 30 Jul 2019 16:03:33 +0800
Subject: [PATCH 004/475] Update README.md
---
docs/README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/README.md b/docs/README.md
index d5dc4e53..328b1fe4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -38,8 +38,12 @@
本项目基于 MIT 协议开源。
+## 友情链接
+
+Effective Java 第 3 版: https://github.com/sjsdfg/effective-java-3rd-chinese
+
## 联系方式
- E-mail :
-
\ No newline at end of file
+
From cdb1605b03b8d0455270b312caa317d2880b13a5 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Tue, 30 Jul 2019 16:11:00 +0800
Subject: [PATCH 005/475] Update 10-Interfaces.md
---
docs/book/10-Interfaces.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md
index aff455e0..754d18d2 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -1299,7 +1299,7 @@ import java.nio.*;
import java.util.*;
public class RandomStrings implements Readable {
- private statdic Random rand = new Random(47);
+ private static Random rand = new Random(47);
private static final char[] CAPITALS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char[] LOWERS = "abcdefghijklmnopqrstuvwxyz".toCharArray();
private static final char[] VOWELS = "aeiou".toCharArray();
@@ -1815,4 +1815,4 @@ Chess move 3
-
\ No newline at end of file
+
From beec799c94aa48c702aea30eb5446645635e166f Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Tue, 30 Jul 2019 14:45:06 +0800
Subject: [PATCH 006/475] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E9=9B=86?=
=?UTF-8?q?=E5=90=88=E4=B8=BB=E9=A2=98=20=E7=BF=BB=E8=AF=91=E6=9B=B4?=
=?UTF-8?q?=E6=96=B0=E8=87=B3=20=E9=9B=86=E5=90=88=E5=B7=A5=E5=85=B7?=
=?UTF-8?q?=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Collection-Topics.md | 314 ++++++++++++++++++++++++
1 file changed, 314 insertions(+)
diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md
index becdfba2..2f095f98 100644
--- a/docs/book/Appendix-Collection-Topics.md
+++ b/docs/book/Appendix-Collection-Topics.md
@@ -2512,6 +2512,320 @@ public class LinkedHashMapDemo {
## 集合工具类
+集合有许多独立的实用工具程序,在 **java.util.Collections** 中表示为静态方法。之前已经见过其中一些,例如 `addAll()` , `reverseOrder()` 和 `binarySearch()` 。以下是其他内容(同步和不可修改的实用工具程序将在后面的章节中介绍)。在此表中,在需要的时候使用了泛型:
+
+| 方法 | 描述 |
+| :--- | :--- |
+| **checkedCollection(Collection\ c, Class\ type)**
**checkedList(List\ list, Class\ type)**
**checkedMap(Map\ m, Class\ keyType, Class\ valueType)**
**checkedSet(Set\ s, Class\ type)**
**checkedSortedMap(SortedMap\ m, Class\ keyType, Class\ valueType)**
**checkedSortedSet(SortedSet\ s, Class\ type)** | 生成 **Collection** 的动态类型安全视图或 **Collection** 的特定子类型。 当无法使用静态检查版本时使用这个版本。
这些方法的使用在[第九章 多态]()章节的“动态类型安全”标题下进行了展示。 |
+| **max(Collection)**
**min(Collection)** | 使用 **Collection** 中对象的自然比较方法生成参数集合中的最大或最小元素。 |
+| **max(Collection, Comparator)**
**min(Collection, Comparator)** | 使用 **Comparator** 指定的比较方法生成参数集合中的最大或最小元素。 |
+| **indexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内第一次出现的起始索引,如果不存在则返回 -1。 |
+| **lastIndexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内最后一次出现的起始索引,如果不存在则返回 -1。 |
+| **replaceAll(List\ list, T oldVal, T newVal)** | 用 **newVal** 替换列表中所有的 **oldVal** 。 |
+| **reverse(List)**| 反转列表 |
+| **reverseOrder()**
**reverseOrder(Comparator\)** | 返回一个 **Comparator** ,它与集合中实现了 **comparable\** 接口的对象的自然顺序相反。第二个版本颠倒了所提供的 **Comparator** 的顺序。 |
+| **rotate(List, int distance)** | 将所有元素向前移动 **distance** ,将尾部的元素移到开头。(译者注:即循环移动) |
+| **shuffle(List)**
**shuffle(List, Random)** | 随机置换指定列表(即打乱顺序)。第一个版本使用了默认的随机化源,或者也可以使用第二个版本,提供自己的随机化源。 |
+| **sort(List\)**
**sort(List\, Comparator\ super T> c)** | 第一个版本使用元素的自然顺序排序该 **List\** 。第二个版本根据提供的 **Comparator** 排序。 |
+| **copy(List\ super T> dest, List\ extends T> src)** | 将 **src** 中的元素复制到 **dest** 。 |
+| **swap(List, int i, int j)** | 交换 **List** 中位置 **i** 和 位置 **j** 的元素。可能比你手工编写的速度快。 |
+| **fill(List\ super T>, T x)** | 用 **x** 替换 **List** 中的所有元素。|
+| **nCopies(int n, T x)** | 返回大小为 **n** 的不可变 **List\** ,其引用都指向 **x** 。 |
+| **disjoint(Collection, Collection)** | 如果两个集合没有共同元素,则返回 **true** 。 |
+| **frequency(Collection, Object x)** | 返回 **Collection** 中,等于 **x** 的元素个数。 |
+| **emptyList()**
**emptyMap()**
**emptySet()** | 返回不可变的空 **List** , **Map** 或 **Set** 。这些是泛型的,因此生成的 **Collection** 可以被参数化为所需的类型。 |
+| **singleton(T x)**
**singletonList(T x)**
**singletonMap(K key, V value)** | 生成一个不可变的 **List** , **Set** 或 **Map** ,其中只包含基于给定参数的单个元素。 |
+| **list(Enumeration\ e)** | 生成一个 **ArrayList\** ,其中元素为(旧式) **Enumeration** ( **Iterator** 的前身)中的元素。用于从遗留代码向新式转换。 |
+| **enumeration(Collection\)** | 为参数集合生成一个旧式的 **Enumeration\** 。 |
+
+请注意, `min()` 和 `max()` 使用 **Collection** 对象,而不使用 **List** ,因此不必担心是否应对 **Collection** 进行排序。(如前所述,在执行 `binarySearch()` 之前,将会对 **List** 或数组进行`sort()` 排序。)
+
+下面是一个示例,展示了上表中大多数实用工具程序的基本用法:
+
+```java
+// collectiontopics/Utilities.java
+// Simple demonstrations of the Collections utilities
+import java.util.*;
+
+public class Utilities {
+ static List list = Arrays.asList(
+ "one Two three Four five six one".split(" "));
+ public static void main(String[] args) {
+ System.out.println(list);
+ System.out.println("'list' disjoint (Four)?: " +
+ Collections.disjoint(list,
+ Collections.singletonList("Four")));
+ System.out.println(
+ "max: " + Collections.max(list));
+ System.out.println(
+ "min: " + Collections.min(list));
+ System.out.println(
+ "max w/ comparator: " + Collections.max(list,
+ String.CASE_INSENSITIVE_ORDER));
+ System.out.println(
+ "min w/ comparator: " + Collections.min(list,
+ String.CASE_INSENSITIVE_ORDER));
+ List sublist =
+ Arrays.asList("Four five six".split(" "));
+ System.out.println("indexOfSubList: " +
+ Collections.indexOfSubList(list, sublist));
+ System.out.println("lastIndexOfSubList: " +
+ Collections.lastIndexOfSubList(list, sublist));
+ Collections.replaceAll(list, "one", "Yo");
+ System.out.println("replaceAll: " + list);
+ Collections.reverse(list);
+ System.out.println("reverse: " + list);
+ Collections.rotate(list, 3);
+ System.out.println("rotate: " + list);
+ List source =
+ Arrays.asList("in the matrix".split(" "));
+ Collections.copy(list, source);
+ System.out.println("copy: " + list);
+ Collections.swap(list, 0, list.size() - 1);
+ System.out.println("swap: " + list);
+ Collections.shuffle(list, new Random(47));
+ System.out.println("shuffled: " + list);
+ Collections.fill(list, "pop");
+ System.out.println("fill: " + list);
+ System.out.println("frequency of 'pop': " +
+ Collections.frequency(list, "pop"));
+ List dups =
+ Collections.nCopies(3, "snap");
+ System.out.println("dups: " + dups);
+ System.out.println("'list' disjoint 'dups'?: " +
+ Collections.disjoint(list, dups));
+ // Getting an old-style Enumeration:
+ Enumeration e =
+ Collections.enumeration(dups);
+ Vector v = new Vector<>();
+ while(e.hasMoreElements())
+ v.addElement(e.nextElement());
+ // Converting an old-style Vector
+ // to a List via an Enumeration:
+ ArrayList arrayList =
+ Collections.list(v.elements());
+ System.out.println("arrayList: " + arrayList);
+ }
+}
+/* Output:
+[one, Two, three, Four, five, six, one]
+'list' disjoint (Four)?: false
+max: three
+min: Four
+max w/ comparator: Two
+min w/ comparator: five
+indexOfSubList: 3
+lastIndexOfSubList: 3
+replaceAll: [Yo, Two, three, Four, five, six, Yo]
+reverse: [Yo, six, five, Four, three, Two, Yo]
+rotate: [three, Two, Yo, Yo, six, five, Four]
+copy: [in, the, matrix, Yo, six, five, Four]
+swap: [Four, the, matrix, Yo, six, five, in]
+shuffled: [six, matrix, the, Four, Yo, five, in]
+fill: [pop, pop, pop, pop, pop, pop, pop]
+frequency of 'pop': 7
+dups: [snap, snap, snap]
+'list' disjoint 'dups'?: true
+arrayList: [snap, snap, snap]
+*/
+```
+
+输出解释了每种实用方法的行为。请注意由于大小写的缘故,普通版本的 `min()` 和 `max()` 与带有 **String.CASE_INSENSITIVE_ORDER** 比较器参数的版本的区别。
+
+
+### 排序和搜索列表
+
+用于执行排序和搜索 **List** 的实用工具程序与用于排序对象数组的程序具有相同的名字和方法签名,只不过是 **Collections** 的静态方法而不是 **Arrays** 。 这是一个使用 **Utilities.java** 中的 **list** 数据的示例:
+
+```java
+// collectiontopics/ListSortSearch.java
+// Sorting/searching Lists with Collections utilities
+import java.util.*;
+
+public class ListSortSearch {
+ public static void main(String[] args) {
+ List list =
+ new ArrayList<>(Utilities.list);
+ list.addAll(Utilities.list);
+ System.out.println(list);
+ Collections.shuffle(list, new Random(47));
+ System.out.println("Shuffled: " + list);
+ // Use ListIterator to trim off last elements:
+ ListIterator it = list.listIterator(10);
+ while(it.hasNext()) {
+ it.next();
+ it.remove();
+ }
+ System.out.println("Trimmed: " + list);
+ Collections.sort(list);
+ System.out.println("Sorted: " + list);
+ String key = list.get(7);
+ int index = Collections.binarySearch(list, key);
+ System.out.println(
+ "Location of " + key + " is " + index +
+ ", list.get(" + index + ") = " +
+ list.get(index));
+ Collections.sort(list,
+ String.CASE_INSENSITIVE_ORDER);
+ System.out.println(
+ "Case-insensitive sorted: " + list);
+ key = list.get(7);
+ index = Collections.binarySearch(list, key,
+ String.CASE_INSENSITIVE_ORDER);
+ System.out.println(
+ "Location of " + key + " is " + index +
+ ", list.get(" + index + ") = " +
+ list.get(index));
+ }
+}
+/* Output:
+[one, Two, three, Four, five, six, one, one, Two,
+three, Four, five, six, one]
+Shuffled: [Four, five, one, one, Two, six, six, three,
+three, five, Four, Two, one, one]
+Trimmed: [Four, five, one, one, Two, six, six, three,
+three, five]
+Sorted: [Four, Two, five, five, one, one, six, six,
+three, three]
+Location of six is 7, list.get(7) = six
+Case-insensitive sorted: [five, five, Four, one, one,
+six, six, three, three, Two]
+Location of three is 7, list.get(7) = three
+*/
+```
+
+就像使用数组进行搜索和排序一样,如果使用 **Comparator** 进行排序,则必须使用相同的 **Comparator** 执行 `binarySearch()` 。
+
+该程序还演示了 **Collections** 中的 `shuffle()` 方法,该方法随机打乱了 **List** 的顺序。 **ListIterator** 是在打乱后的列表中的特定位置创建的,用于从该位置删除元素,直到列表末尾。
+
+
+### 创建不可修改的 Collection 或 Map
+
+通常,创建 **Collection** 或 **Map** 的只读版本会很方便。 **Collections** 类通过将原始集合传递给一个方法然后返回一个只读版本的集合。 对于 **Collection** (如果不能将 **Collection** 视为更具体的类型), **List** , **Set** 和 **Map** ,这类方法有许多变体。这个示例展示了针对每种类型,正确构建只读版本集合的方法:
+
+```java
+// collectiontopics/ReadOnly.java
+// Using the Collections.unmodifiable methods
+import java.util.*;
+import onjava.*;
+
+public class ReadOnly {
+ static Collection data =
+ new ArrayList<>(Countries.names(6));
+ public static void main(String[] args) {
+ Collection c =
+ Collections.unmodifiableCollection(
+ new ArrayList<>(data));
+ System.out.println(c); // Reading is OK
+ //- c.add("one"); // Can't change it
+
+ List a = Collections.unmodifiableList(
+ new ArrayList<>(data));
+ ListIterator lit = a.listIterator();
+ System.out.println(lit.next()); // Reading is OK
+ //- lit.add("one"); // Can't change it
+
+ Set s = Collections.unmodifiableSet(
+ new HashSet<>(data));
+ System.out.println(s); // Reading is OK
+ //- s.add("one"); // Can't change it
+
+ // For a SortedSet:
+ Set ss =
+ Collections.unmodifiableSortedSet(
+ new TreeSet<>(data));
+
+ Map m =
+ Collections.unmodifiableMap(
+ new HashMap<>(Countries.capitals(6)));
+ System.out.println(m); // Reading is OK
+ //- m.put("Ralph", "Howdy!");
+
+ // For a SortedMap:
+ Map sm =
+ Collections.unmodifiableSortedMap(
+ new TreeMap<>(Countries.capitals(6)));
+ }
+}
+/* Output:
+[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,
+BURUNDI]
+ALGERIA
+[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,
+BURUNDI]
+{BENIN=Porto-Novo, BOTSWANA=Gaberone, ANGOLA=Luanda,
+BURKINA FASO=Ouagadougou, ALGERIA=Algiers,
+BURUNDI=Bujumbura}
+*/
+```
+
+为特定类型调用 “unmodifiable” 方法不会导致编译时检查,但是一旦发生转换,对修改特定集合内容的任何方法调用都将产生 **UnsupportedOperationException** 异常。
+
+在每种情况下,在将集合设置为只读之前,必须使用有意义的数据填充集合。填充完成后,最好的方法是用 “unmodifiable” 方法调用生成的引用替换现有引用。这样,一旦使得内容无法修改,那么就不会冒有意外更改内容的风险。另一方面,此工具还允许将可修改的集合保留为类中的**私有**集合,并从方法调用处返回对该集合的只读引用。所以,你可以在类内修改它,但其他人只能读它。
+
+
+### 同步 Collection 或 Map
+
+**synchronized** 关键字是多线程主题的重要组成部分,更复杂的内容在[第二十四章 并发编程]()中介绍。在这里,只需要注意到 **Collections** 类包含一种自动同步整个集合的方法。 语法类似于 “unmodifiable” 方法:
+
+```java
+// collectiontopics/Synchronization.java
+// Using the Collections.synchronized methods
+import java.util.*;
+
+public class Synchronization {
+ public static void main(String[] args) {
+ Collection c =
+ Collections.synchronizedCollection(
+ new ArrayList<>());
+ List list = Collections
+ .synchronizedList(new ArrayList<>());
+ Set s = Collections
+ .synchronizedSet(new HashSet<>());
+ Set ss = Collections
+ .synchronizedSortedSet(new TreeSet<>());
+ Map m = Collections
+ .synchronizedMap(new HashMap<>());
+ Map sm = Collections
+ .synchronizedSortedMap(new TreeMap<>());
+ }
+}
+```
+
+最好立即通过适当的 “synchronized” 方法传递新集合,如上所示。这样,就不会意外地暴露出非同步版本。
+
+
+#### Fail Fast
+
+Java 集合还具有防止多个进程修改集合内容的机制。如果当前正在迭代集合,然后有其他一些进程介入并插入,删除或更改该集合中的对象,则会出现此问题。也许在集合中已经遍历过了那个元素,也许还没有遍历到,也许在调用 `size()` 之后集合的大小会缩小...有许多灾难情景。 Java 集合库使用一种 *fail-fast* 的机制,该机制可以检测到除了当前进程引起的更改之外,其它任何对集合的更改操作。如果它检测到其他人正在修改集合,则会立即生成 **ConcurrentModificationException** 异常。这就是“fail-fast”的含义——它不会在以后使用更复杂的算法尝试检测问题(快速失败)。
+
+通过创建迭代器并向迭代器指向的集合中添加元素,可以很容易地看到操作中的 fail-fast 机制,如下所示:
+
+```java
+// collectiontopics/FailFast.java
+// Demonstrates the "fail-fast" behavior
+import java.util.*;
+
+public class FailFast {
+ public static void main(String[] args) {
+ Collection c = new ArrayList<>();
+ Iterator it = c.iterator();
+ c.add("An object");
+ try {
+ String s = it.next();
+ } catch(ConcurrentModificationException e) {
+ System.out.println(e);
+ }
+ }
+}
+/* Output:
+java.util.ConcurrentModificationException
+*/
+```
+
+异常来自于在从集合中获得迭代器之后,又尝试在集合中添加元素。程序的两个部分可能会修改同一个集合,这种可能性的存在会产生不确定状态,因此异常会通知你更改代码。在这种情况下,应先将所有元素添加到集合,然后再获取迭代器。
+
+**ConcurrentHashMap** , **CopyOnWriteArrayList** 和 **CopyOnWriteArraySet** 使用了特定的技术来避免产生 **ConcurrentModificationException** 异常。
## 持有引用
From e545254dc04cc373c87659062ca9aa6295ac66ac Mon Sep 17 00:00:00 2001
From: blackwatch
Date: Wed, 31 Jul 2019 10:47:55 +0800
Subject: [PATCH 007/475] Parallel Streams Only LookEasy
---
docs/book/24-Concurrent-Programming.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md
index 2f8f3a6e..1c7b66cf 100644
--- a/docs/book/24-Concurrent-Programming.md
+++ b/docs/book/24-Concurrent-Programming.md
@@ -711,6 +711,7 @@ public class ParallelStreamPuzzle3 {
- 并行流只看起来很容易
实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如您所见,只需将**parallel()**打到您的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,您必须了解并行性如何帮助或损害您的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着您不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现您的目标。
+
## 创建和运行任务
From 27d8061c56b48be99cf56a15916aff1f68071f13 Mon Sep 17 00:00:00 2001
From: blackwatch
Date: Wed, 31 Jul 2019 10:48:11 +0800
Subject: [PATCH 008/475] Parallel Streams Only LookEasy
---
docs/book/24-Concurrent-Programming.md | 476 +++++++++++++++++++++++++
1 file changed, 476 insertions(+)
diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md
index 1c7b66cf..436c9521 100644
--- a/docs/book/24-Concurrent-Programming.md
+++ b/docs/book/24-Concurrent-Programming.md
@@ -715,7 +715,483 @@ public class ParallelStreamPuzzle3 {
## 创建和运行任务
+如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后您将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。
+Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。
+
+- Tasks and Executors
+
+在Java的早期版本中,您通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建您自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。
+
+创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为您处理线程池。您可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为您管理线程,并且在运行任务后重新循环线程而不是丢弃线程。
+
+首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成:
+
+```java
+// concurrent/NapTask.java
+import onjava.Nap;
+public class NapTask implements Runnable {
+ finalint id;
+ public NapTask(int id) {
+ this.id = id;
+ }
+ @Override
+ public void run() {
+ new Nap(0.1);// Seconds
+ System.out.println(this + " "+
+ Thread.currentThread().getName());
+ }
+ @Override
+ public String toString() {
+ return"NapTask[" + id + "]";
+ }
+}
+```
+
+这只是一个**Runnable**:一个包含**run()**方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”:
+
+```java
+// onjava/Nap.java
+package onjava;
+import java.util.concurrent.*;
+public class Nap {
+ public Nap(double t) { // Seconds
+ try {
+ TimeUnit.MILLISECONDS.sleep((int)(1000 * t));
+ } catch(InterruptedException e){
+ throw new RuntimeException(e);
+ }
+ }
+ public Nap(double t, String msg) {
+ this(t);
+ System.out.println(msg);
+ }
+}
+```
+为了消除异常处理的视觉噪声,这被定义为实用程序。第二个构造函数在超时时显示一条消息
+
+对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在您的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。
+
+你可以看到**sleep()**抛出一个已检查的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。
+
+要执行任务,我们将从最简单的方法--SingleThreadExecutor开始:
+
+```java
+/ concurrent/SingleThreadExecutor.java
+import java.util.concurrent.*;
+import java.util.stream.*;
+import onjava.*;
+public class SingleThreadExecutor {
+ public static void main(String[] args) {
+ ExecutorService exec =
+ Executors.newSingleThreadExecutor();
+ IntStream.range(0, 10)
+ .mapToObj(NapTask::new)
+ .forEach(exec::execute);
+ System.out.println("All tasks submitted");
+ exec.shutdown();
+ while(!exec.isTerminated()) {
+ System.out.println(
+ Thread.currentThread().getName()+
+ " awaiting termination");
+ new Nap(0.1);
+ }
+ }
+}
+/* Output:
+All tasks submitted
+main awaiting termination
+main awaiting termination
+NapTask[0] pool-1-thread-1
+main awaiting termination
+NapTask[1] pool-1-thread-1
+main awaiting termination
+NapTask[2] pool-1-thread-1
+main awaiting termination
+NapTask[3] pool-1-thread-1
+main awaiting termination
+NapTask[4] pool-1-thread-1
+main awaiting termination
+NapTask[5] pool-1-thread-1
+main awaiting termination
+NapTask[6] pool-1-thread-1
+main awaiting termination
+NapTask[7] pool-1-thread-1
+main awaiting termination
+NapTask[8] pool-1-thread-1
+main awaiting termination
+NapTask[9] pool-1-thread-1
+*/
+```
+
+首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4]
+
+我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。
+
+请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。
+
+如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,虽然不需要(!exec.isTerminated())。
+
+```java
+// concurrent/SingleThreadExecutor2.java
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class SingleThreadExecutor2 {
+ public static void main(String[] args)throws InterruptedException {
+ ExecutorService exec
+ =Executors.newSingleThreadExecutor();
+ IntStream.range(0, 10)
+ .mapToObj(NapTask::new)
+ .forEach(exec::execute);
+ exec.shutdown();
+ }
+}
+/* Output:
+NapTask[0] pool-1-thread-1
+NapTask[1] pool-1-thread-1
+NapTask[2] pool-1-thread-1
+NapTask[3] pool-1-thread-1
+NapTask[4] pool-1-thread-1
+NapTask[5] pool-1-thread-1
+NapTask[6] pool-1-thread-1
+NapTask[7] pool-1-thread-1
+NapTask[8] pool-1-thread-1
+NapTask[9] pool-1-thread-1
+*/
+```
+
+一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。
+
+```java
+// concurrent/MoreTasksAfterShutdown.java
+import java.util.concurrent.*;
+public class MoreTasksAfterShutdown {
+ public static void main(String[] args) {
+ ExecutorService exec
+ =Executors.newSingleThreadExecutor();
+ exec.execute(newNapTask(1));
+ exec.shutdown();
+ try {
+ exec.execute(newNapTask(99));
+ } catch(RejectedExecutionException e) {
+ System.out.println(e);
+ }
+ }
+}
+/* Output:
+java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1
+*/
+
+```
+
+**exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。
+
+- 使用更多线程
+
+使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,您将看到更多选项。例如CachedThreadPool:
+
+```java
+// concurrent/CachedThreadPool.java
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class CachedThreadPool {
+ public static void main(String[] args) {
+ ExecutorService exec
+ =Executors.newCachedThreadPool();
+ IntStream.range(0, 10)
+ .mapToObj(NapTask::new)
+ .forEach(exec::execute);
+ exec.shutdown();
+ }
+}
+/* Output:
+NapTask[7] pool-1-thread-8
+NapTask[4] pool-1-thread-5
+NapTask[1] pool-1-thread-2
+NapTask[3] pool-1-thread-4
+NapTask[0] pool-1-thread-1
+NapTask[8] pool-1-thread-9
+NapTask[2] pool-1-thread-3
+NapTask[9] pool-1-thread-10
+NapTask[6] pool-1-thread-7
+NapTask[5] pool-1-thread-6
+*/
+
+```
+
+当你运行这个程序时,你会发现它完成得更快。这是有道理的,而不是使用相同的线程来顺序运行每个任务,每个任务都有自己的线程,所以它们都并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。
+
+要理解这个问题,我们需要一个更复杂的任务:
+
+```java
+// concurrent/InterferingTask.java
+public class InterferingTask implements Runnable {
+ final int id;
+ private static Integer val = 0;
+ public InterferingTask(int id) {
+ this.id = id;
+ }
+ @Override
+ public void run() {
+ for(int i = 0; i < 100; i++)
+ val++;
+ System.out.println(id + " "+
+ Thread.currentThread().getName() + " " + val);
+ }
+}
+
+```
+
+每个任务增加val一百次。这似乎很简单。让我们用CachedThreadPool尝试一下:
+
+```java
+// concurrent/CachedThreadPool2.java
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class CachedThreadPool2 {
+ public static void main(String[] args) {
+ ExecutorService exec
+ =Executors.newCachedThreadPool();
+ IntStream.range(0, 10)
+ .mapToObj(InterferingTask::new)
+ .forEach(exec::execute);
+ exec.shutdown();
+ }
+}
+/* Output:
+0 pool-1-thread-1 200
+1 pool-1-thread-2 200
+4 pool-1-thread-5 300
+5 pool-1-thread-6 400
+8 pool-1-thread-9 500
+9 pool-1-thread-10 600
+2 pool-1-thread-3 700
+7 pool-1-thread-8 800
+3 pool-1-thread-4 900
+6 pool-1-thread-7 1000
+*/
+
+```
+
+输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们说这样的类不是线程安全的。让我们看看SingleThreadExecutor会发生什么:
+
+```java
+// concurrent/SingleThreadExecutor3.java
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class SingleThreadExecutor3 {
+ public static void main(String[] args)throws InterruptedException {
+ ExecutorService exec
+ =Executors.newSingleThreadExecutor();
+ IntStream.range(0, 10)
+ .mapToObj(InterferingTask::new)
+ .forEach(exec::execute);
+ exec.shutdown();
+ }
+}
+/* Output:
+0 pool-1-thread-1 100
+1 pool-1-thread-1 200
+2 pool-1-thread-1 300
+3 pool-1-thread-1 400
+4 pool-1-thread-1 500
+5 pool-1-thread-1 600
+6 pool-1-thread-1 700
+7 pool-1-thread-1 800
+8 pool-1-thread-1 900
+9 pool-1-thread-1 1000
+*/
+
+```
+
+现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。线程限制限制了加速,但可以节省很多困难的调试和重写。
+
+- 产生结果
+
+因为**InterferingTask**是一个**Runnable**,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了**CachedThreadPool2.java**。**InterferingTask**中的**val**被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。
+
+避免竞争条件的最好方法是避免可变的共享状态。我们可以称之为自私的孩子原则:什么都不分享。
+
+使用**InterferingTask**,最好删除副作用并返回任务结果。为此,我们创建**Callable**而不是**Runnable**:
+
+```java
+// concurrent/CountingTask.java
+import java.util.concurrent.*;
+public class CountingTask implements Callable {
+ final int id;
+ public CountingTask(int id) { this.id = id; }
+ @Override
+ public Integer call() {
+ Integer val = 0;
+ for(int i = 0; i < 100; i++)
+ val++;
+ System.out.println(id + " " +
+ Thread.currentThread().getName() + " " + val);
+ return val;
+ }
+}
+
+```
+
+**call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态
+
+**ExecutorService**允许您使用**invokeAll()**启动集合中的每个Callable:
+
+```java
+// concurrent/CachedThreadPool3.java
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class CachedThreadPool3 {
+ public static Integer extractResult(Future f) {
+ try {
+ return f.get();
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ public static void main(String[] args)throws InterruptedException {
+ ExecutorService exec =
+ Executors.newCachedThreadPool();
+ List tasks =
+ IntStream.range(0, 10)
+ .mapToObj(CountingTask::new)
+ .collect(Collectors.toList());
+ List> futures =
+ exec.invokeAll(tasks);
+ Integer sum = futures.stream()
+ .map(CachedThreadPool3::extractResult)
+ .reduce(0, Integer::sum);
+ System.out.println("sum = " + sum);
+ exec.shutdown();
+ }
+}
+/* Output:
+1 pool-1-thread-2 100
+0 pool-1-thread-1 100
+4 pool-1-thread-5 100
+5 pool-1-thread-6 100
+8 pool-1-thread-9 100
+9 pool-1-thread-10 100
+2 pool-1-thread-3 100
+3 pool-1-thread-4 100
+6 pool-1-thread-7 100
+7 pool-1-thread-8 100
+sum = 1000
+*/
+
+```
+
+只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许您提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**:
+
+```java
+// concurrent/Futures.java
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class Futures {
+ public static void main(String[] args)throws InterruptedException, ExecutionException {
+ ExecutorService exec
+ =Executors.newSingleThreadExecutor();
+ Future f =
+ exec.submit(newCountingTask(99));
+ System.out.println(f.get()); // [1]
+ exec.shutdown();
+ }
+}
+/* Output:
+99 pool-1-thread-1 100
+100
+*/
+```
+
+- [1] 当你的任务尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。
+
+但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。
+
+还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。
+
+因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,您仍会在遗留库中遇到Futures
+
+我们可以使用并行Stream以更简单,更优雅的方式解决这个问题:
+
+```java
+// concurrent/CountingStream.java
+// {VisuallyInspectOutput}
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.stream.*;
+public class CountingStream {
+ public static void main(String[] args) {
+ System.out.println(
+ IntStream.range(0, 10)
+ .parallel()
+ .mapToObj(CountingTask::new)
+ .map(ct -> ct.call())
+ .reduce(0, Integer::sum));
+ }
+}
+/* Output:
+1 ForkJoinPool.commonPool-worker-3 100
+8 ForkJoinPool.commonPool-worker-2 100
+0 ForkJoinPool.commonPool-worker-6 100
+2 ForkJoinPool.commonPool-worker-1 100
+4 ForkJoinPool.commonPool-worker-5 100
+9 ForkJoinPool.commonPool-worker-7 100
+6 main 100
+7 ForkJoinPool.commonPool-worker-4 100
+5 ForkJoinPool.commonPool-worker-2 100
+3 ForkJoinPool.commonPool-worker-3 100
+1000
+*/
+```
+
+这不仅更容易理解,我们需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。
+
+- Lambda和方法引用作为任务
+
+使用lambdas和方法引用,您不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将notRunnables或Callables的参数传递给ExecutorService:
+
+使用lambdas和方法引用,您不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将不是**Runnables**或**Callables**的参数传递给**ExecutorService**:
+
+```java
+// concurrent/LambdasAndMethodReferences.java
+import java.util.concurrent.*;
+class NotRunnable {
+ public void go() {
+ System.out.println("NotRunnable");
+ }
+}
+class NotCallable {
+ public Integer get() {
+ System.out.println("NotCallable");
+ return 1;
+ }
+}
+public class LambdasAndMethodReferences {
+ public static void main(String[] args)throws InterruptedException {
+ ExecutorService exec =
+ Executors.newCachedThreadPool();
+ exec.submit(() -> System.out.println("Lambda1"));
+ exec.submit(newNotRunnable()::go);
+ exec.submit(() -> {
+ System.out.println("Lambda2");
+ return 1;
+ });
+ exec.submit(newNotCallable()::get);
+ exec.shutdown();
+ }
+}
+/* Output:
+Lambda1
+NotCallable
+NotRunnable
+Lambda2
+*/
+
+```
+
+这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,您可以在后两次调用的情况下提取结果。
## 终止耗时任务
From e2f302ab08ec898ff44bc91c346e40e3543fc743 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Wed, 31 Jul 2019 11:45:13 +0800
Subject: [PATCH 009/475] Update 01-What-is-an-Object.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
图中是三角形而非菱形
---
docs/book/01-What-is-an-Object.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md
index 61a5a556..3a3f0177 100644
--- a/docs/book/01-What-is-an-Object.md
+++ b/docs/book/01-What-is-an-Object.md
@@ -104,7 +104,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开

-上图中实心菱形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心菱形。
+上图中实心三角形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心三角形。
(**译者注**:组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义。至于是聚合还是组合,需要根据实际的业务需求来判断。可能相同超类和子类,在不同的业务场景,关联关系会发生变化。只看代码是无法区分聚合和组合的,具体是哪一种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。)
From f9de889bbedf50c67ee893587ff2b8074032737d Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Wed, 31 Jul 2019 16:46:42 +0800
Subject: [PATCH 010/475] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E9=9B=86?=
=?UTF-8?q?=E5=90=88=E4=B8=BB=E9=A2=98=20=E7=BF=BB=E8=AF=91=E5=AE=8C?=
=?UTF-8?q?=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Collection-Topics.md | 385 +++++++++++++++++++++++-
1 file changed, 384 insertions(+), 1 deletion(-)
diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md
index 2f095f98..22ce7a6a 100644
--- a/docs/book/Appendix-Collection-Topics.md
+++ b/docs/book/Appendix-Collection-Topics.md
@@ -2830,16 +2830,399 @@ java.util.ConcurrentModificationException
## 持有引用
+**java.lang.ref** 中库包含一组类,这些类允许垃圾收集具有更大的灵活性。特别是当拥有可能导致内存耗尽的大对象时,这些类特别有用。这里有三个从抽象类 **Reference** 继承来的类: **SoftReference** (软引用), **WeakReference** (弱引用)和 **PhantomReference** (虚引用)继承了三个类。如果一个对象只能通过这其中的一个 **Reference** 对象访问,那么这三种类型每个都为垃圾收集器提供不同级别的间接引用(indirection)。
+
+如果一个对象是 *可达的*(reachable),那么意味着在程序中的某个位置可以找到该对象。这可能意味着在栈上有一个直接引用该对象的普通引用,但也有可能是引用了一个对该对象有引用的对象,这可以有很多中间环节。如果某个对象是可达的,则垃圾收集器无法释放它,因为它仍然被程序所使用。如果某个对象是不可达的,则程序无法使用它,那么垃圾收集器回收该对象就是安全的。
+
+使用 **Reference** 对象继续保持对该对象的引用,以到达该对象,但也允许垃圾收集器释放该对象。因此,程序可以使用该对象,但如果内存即将耗尽,则允许释放该对象。
+
+可以通过使用 **Reference** 对象作为你和普通引用之间的中介(代理)来实现此目的。此外,必须没有对象的普通引用(未包含在 **Reference** 对象中的对象)。如果垃圾收集器发现对象可通过普通引用访问,则它不会释放该对象。
+
+按照 **SoftReference** , **WeakReference** 和 **PhantomReference** 的顺序,每个都比前一个更“弱”,并且对应于不同的可达性级别。软引用用于实现对内存敏感的缓存。弱引用用于实现“规范化映射”( canonicalized mappings)——对象的实例可以在程序的多个位置同时使用,以节省存储,但不会阻止其键(或值)被回收。虚引用用于调度 pre-mortem 清理操作,这是一种比 Java 终结机制(Java finalization mechanism)更灵活的方式。
+
+使用 **SoftReference** 和 **WeakReference** ,可以选择是否将它们放在 **ReferenceQueue** (用于 pre-mortem 清理操作的设备)中,但 **PhantomReference** 只能在 **ReferenceQueue** 上构建。下面是一个简单的演示:
+
+```java
+// collectiontopics/References.java
+// Demonstrates Reference objects
+import java.lang.ref.*;
+import java.util.*;
+
+class VeryBig {
+ private static final int SIZE = 10000;
+ private long[] la = new long[SIZE];
+ private String ident;
+ VeryBig(String id) { ident = id; }
+ @Override
+ public String toString() { return ident; }
+ @Override
+ protected void finalize() {
+ System.out.println("Finalizing " + ident);
+ }
+}
+
+public class References {
+ private static ReferenceQueue rq =
+ new ReferenceQueue<>();
+ public static void checkQueue() {
+ Reference extends VeryBig> inq = rq.poll();
+ if(inq != null)
+ System.out.println("In queue: " + inq.get());
+ }
+ public static void main(String[] args) {
+ int size = 10;
+ // Or, choose size via the command line:
+ if(args.length > 0)
+ size = Integer.valueOf(args[0]);
+ LinkedList> sa =
+ new LinkedList<>();
+ for(int i = 0; i < size; i++) {
+ sa.add(new SoftReference<>(
+ new VeryBig("Soft " + i), rq));
+ System.out.println(
+ "Just created: " + sa.getLast());
+ checkQueue();
+ }
+ LinkedList> wa =
+ new LinkedList<>();
+ for(int i = 0; i < size; i++) {
+ wa.add(new WeakReference<>(
+ new VeryBig("Weak " + i), rq));
+ System.out.println(
+ "Just created: " + wa.getLast());
+ checkQueue();
+ }
+ SoftReference s =
+ new SoftReference<>(new VeryBig("Soft"));
+ WeakReference w =
+ new WeakReference<>(new VeryBig("Weak"));
+ System.gc();
+ LinkedList> pa =
+ new LinkedList<>();
+ for(int i = 0; i < size; i++) {
+ pa.add(new PhantomReference<>(
+ new VeryBig("Phantom " + i), rq));
+ System.out.println(
+ "Just created: " + pa.getLast());
+ checkQueue();
+ }
+ }
+}
+/* Output: (First and Last 10 Lines)
+Just created: java.lang.ref.SoftReference@15db9742
+Just created: java.lang.ref.SoftReference@6d06d69c
+Just created: java.lang.ref.SoftReference@7852e922
+Just created: java.lang.ref.SoftReference@4e25154f
+Just created: java.lang.ref.SoftReference@70dea4e
+Just created: java.lang.ref.SoftReference@5c647e05
+Just created: java.lang.ref.SoftReference@33909752
+Just created: java.lang.ref.SoftReference@55f96302
+Just created: java.lang.ref.SoftReference@3d4eac69
+Just created: java.lang.ref.SoftReference@42a57993
+...________...________...________...________...
+Just created: java.lang.ref.PhantomReference@45ee12a7
+In queue: null
+Just created: java.lang.ref.PhantomReference@330bedb4
+In queue: null
+Just created: java.lang.ref.PhantomReference@2503dbd3
+In queue: null
+Just created: java.lang.ref.PhantomReference@4b67cf4d
+In queue: null
+Just created: java.lang.ref.PhantomReference@7ea987ac
+In queue: null
+*/
+```
+
+当运行此程序(将输出重定向到文本文件以查看页面中的输出)时,将会看到对象是被垃圾收集了的,虽然仍然可以通过 **Reference** 对象访问它们(使用 `get()` 来获取实际的对象引用)。 还可以看到 **ReferenceQueue** 始终生成包含 **null** 对象的 **Reference** 。 要使用它,请从特定的 **Reference** 类继承,并为新类添加更多有用的方法。
+
+
+
+### WeakHashMap
+
+集合类库中有一个特殊的 **Map** 来保存弱引用: **WeakHashMap** 。 此类可以更轻松地创建规范化映射。在这种映射中,可以通过仅仅创建一个特定值的实例来节省存储空间。当程序需要该值时,它会查找映射中的现有对象并使用它(而不是从头开始创建一个)。 该映射可以将值作为其初始化的一部分,但更有可能的是在需要时创建该值。
+
+由于这是一种节省存储空间的技术,因此 **WeakHashMap** 允许垃圾收集器自动清理键和值,这是非常方便的。不能对放在 **WeakHashMap** 中的键和值做任何特殊操作,它们由 map 自动包装在 **WeakReference** 中。当键不再被使用的时候才允许清理,如下所示:
+
+```java
+// collectiontopics/CanonicalMapping.java
+// Demonstrates WeakHashMap
+import java.util.*;
+
+class Element {
+ private String ident;
+ Element(String id) { ident = id; }
+ @Override
+ public String toString() { return ident; }
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(ident);
+ }
+ @Override
+ public boolean equals(Object r) {
+ return r instanceof Element &&
+ Objects.equals(ident, ((Element)r).ident);
+ }
+ @Override
+ protected void finalize() {
+ System.out.println("Finalizing " +
+ getClass().getSimpleName() + " " + ident);
+ }
+}
+
+class Key extends Element {
+ Key(String id) { super(id); }
+}
+
+class Value extends Element {
+ Value(String id) { super(id); }
+}
+
+public class CanonicalMapping {
+ public static void main(String[] args) {
+ int size = 1000;
+ // Or, choose size via the command line:
+ if(args.length > 0)
+ size = Integer.valueOf(args[0]);
+ Key[] keys = new Key[size];
+ WeakHashMap map =
+ new WeakHashMap<>();
+ for(int i = 0; i < size; i++) {
+ Key k = new Key(Integer.toString(i));
+ Value v = new Value(Integer.toString(i));
+ if(i % 3 == 0)
+ keys[i] = k; // Save as "real" references
+ map.put(k, v);
+ }
+ System.gc();
+ }
+}
+```
+
+**Key** 类必须具有 `hashCode()` 和 `equals()` ,因为它将被用作散列数据结构中的键。 `hashCode()` 的内容在[附录:理解hashCode和equals方法]()中进行了描述。
+
+运行程序,你会看到垃圾收集器每三个键跳过一次。对该键的普通引用也被放置在 **keys** 数组中,因此这些对象不能被垃圾收集。
-## 避免旧式类库
+## Java 1.0 / 1.1 的集合类
+
+不幸的是,许多代码是使用 Java 1.0 / 1.1 中的集合编写的,甚至新代码有时也是使用这些类编写的。编写新代码时切勿使用旧集合。旧的集合类有限,所以关于它们的讨论不多。由于它们是不合时宜的,所以我会尽量避免过分强调一些可怕的设计决定。
+
+
+### Vector 和 Enumeration
+
+Java 1.0 / 1.1 中唯一的自扩展序列是 **Vector** ,因此它被用于很多地方。它的缺陷太多了,无法在这里描述(参见《Java编程思想》第1版,可从[www.OnJava8.com](www.OnJava8.com)免费下载)。基本上,你可以将它看作是具有冗长且笨拙的方法名称的 **ArrayList** 。在修订后的 Java 集合库中,**Vector** 已经被调整适配过,因此可以作为 **Collection** 和 **List** 来使用。事实证明这有点不正常,集合类库仍然包含它只是为了支持旧的 Java 代码,但这会让一些人误以为 **Vector** 已经变得更好了。
+
+迭代器的 Java 1.0 / 1.1 版本选择创建一个新名称“enumeration”,而不是使用每个人都熟悉的术语(“iterator”)。 **Enumeration** 接口小于 **Iterator** ,只包含两个方法,并且它使用更长的方法名称:如果还有更多元素,则 `boolean hasMoreElements()` 返回 `true` , `Object nextElement()` 返回此enumeration的下一个元素 (否则会抛出异常)。
+
+**Enumeration** 只是一个接口,而不是一个实现,甚至新的类库有时仍然使用旧的 **Enumeration** ,这是不幸的,但通常是无害的。应该总是在自己的代码中使用 **Iterator** ,但要做好准备应对那些提供 **Enumeration** 的类库。
+
+此外,可以使用 `Collections.enumeration()` 方法为任何 **Collection** 生成 **Enumeration** ,如下例所示:
+
+```java
+// collectiontopics/Enumerations.java
+// Java 1.0/1.1 Vector and Enumeration
+import java.util.*;
+import onjava.*;
+
+public class Enumerations {
+ public static void main(String[] args) {
+ Vector v =
+ new Vector<>(Countries.names(10));
+ Enumeration e = v.elements();
+ while(e.hasMoreElements())
+ System.out.print(e.nextElement() + ", ");
+ // Produce an Enumeration from a Collection:
+ e = Collections.enumeration(new ArrayList<>());
+ }
+}
+/* Output:
+ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,
+BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN
+REPUBLIC, CHAD,
+*/
+```
+
+要生成 **Enumeration** ,可以调用 `elements()` ,然后可以使用它来执行向前迭代。
+
+最后一行创建一个 **ArrayList** ,并使用 `enumeration() ` 来将 **ArrayList** 适配为一个 **Enumeration** 。 因此,如果有旧代码需要使用 **Enumeration** ,你仍然可以使用新集合。
+
+
+### Hashtable
+
+正如你在本附录中的性能比较中所看到的,基本的 **Hashtable** 与 **HashMap** 非常相似,甚至方法名称都相似。在新代码中没有理由使用 **Hashtable** 而不是 **HashMap** 。
+
+
+### Stack
+
+之前使用 **LinkedList** 引入了栈的概念。 Java 1.0 / 1.1 **Stack** 的奇怪之处在于,不是以组合方式使用 **Vector** ,而是继承自 **Vector** 。 因此它具有 **Vector** 的所有特征和行为以及一些额外的 **Stack** 行为。很难去知道设计师是否有意识地认为这样做是有用的,或者它是否只是太天真了,无论如何,它在进入发行版之前显然没有经过审查,所以这个糟糕的设计仍然存在(但不要使用它)。
+
+这是 **Stack** 的简单演示,向栈中放入枚举中每一个类型的 **String** 形式。它还展示了如何轻松地将 **LinkedList** 用作栈,或者使用在[第十二章:集合]()章节中创建的 **Stack** 类:
+
+```java
+// collectiontopics/Stacks.java
+// Demonstration of Stack Class
+import java.util.*;
+
+enum Month { JANUARY, FEBRUARY, MARCH, APRIL,
+ MAY, JUNE, JULY, AUGUST, SEPTEMBER,
+ OCTOBER, NOVEMBER }
+
+public class Stacks {
+ public static void main(String[] args) {
+ Stack stack = new Stack<>();
+ for(Month m : Month.values())
+ stack.push(m.toString());
+ System.out.println("stack = " + stack);
+ // Treating a stack as a Vector:
+ stack.addElement("The last line");
+ System.out.println(
+ "element 5 = " + stack.elementAt(5));
+ System.out.println("popping elements:");
+ while(!stack.empty())
+ System.out.print(stack.pop() + " ");
+
+ // Using a LinkedList as a Stack:
+ LinkedList lstack = new LinkedList<>();
+ for(Month m : Month.values())
+ lstack.addFirst(m.toString());
+ System.out.println("lstack = " + lstack);
+ while(!lstack.isEmpty())
+ System.out.print(lstack.removeFirst() + " ");
+
+ // Using the Stack class from
+ // the Collections Chapter:
+ onjava.Stack stack2 =
+ new onjava.Stack<>();
+ for(Month m : Month.values())
+ stack2.push(m.toString());
+ System.out.println("stack2 = " + stack2);
+ while(!stack2.isEmpty())
+ System.out.print(stack2.pop() + " ");
+
+ }
+}
+/* Output:
+stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
+JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER]
+element 5 = JUNE
+popping elements:
+The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY
+JUNE MAY APRIL MARCH FEBRUARY JANUARY lstack =
+[NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY,
+APRIL, MARCH, FEBRUARY, JANUARY]
+NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL
+MARCH FEBRUARY JANUARY stack2 = [NOVEMBER, OCTOBER,
+SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH,
+FEBRUARY, JANUARY]
+NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL
+MARCH FEBRUARY JANUARY
+*/
+```
+
+**String** 形式是由 **Month** 中的枚举常量生成的,使用 `push()` 压入到栈中,然后使用 `pop()` 从栈顶部取出。为了说明一点,将 **Vector** 的操作也在 **Stack** 对象上执行, 这是可能的,因为凭借继承, **Stack** 是 **Vector** 。 因此,可以在 **Vector** 上执行的所有操作也可以在 **Stack** 上执行,例如 `elementAt()` 。
+
+如前所述,在需要栈行为时使用 **LinkedList** ,或者从 **LinkedList** 类创建的 **onjava.Stack** 类。
+
+
+### BitSet
+
+**BitSet** 用于有效地存储大量的开关信息。仅从尺寸大小的角度来看它是有效的,如果你正在寻找有效的访问,它比使用本机数组(native array)稍慢。
+
+此外, **BitSet** 的最小大小是 **long** :64位。这意味着如果你要存储更小的东西,比如8位, **BitSet** 就是浪费,如果尺寸有问题,你最好创建自己的类,或者只是用一个数组来保存你的标志。(只有在你创建许多包含开关信息列表的对象时才会出现这种情况,并且只应根据分析和其他指标来决定。如果你做出此决定只是因为您认为 **BitSet** 太大,那么最终会产生不必要的复杂性并且浪费大量时间。)
+
+当添加更多元素时,普通集合会扩展, **BitSet**也会这样做。以下示例显示了 **BitSet** 的工作原理:
+
+```java
+// collectiontopics/Bits.java
+// Demonstration of BitSet
+import java.util.*;
+
+public class Bits {
+ public static void printBitSet(BitSet b) {
+ System.out.println("bits: " + b);
+ StringBuilder bbits = new StringBuilder();
+ for(int j = 0; j < b.size() ; j++)
+ bbits.append(b.get(j) ? "1" : "0");
+ System.out.println("bit pattern: " + bbits);
+ }
+ public static void main(String[] args) {
+ Random rand = new Random(47);
+ // Take the LSB of nextInt():
+ byte bt = (byte)rand.nextInt();
+ BitSet bb = new BitSet();
+ for(int i = 7; i >= 0; i--)
+ if(((1 << i) & bt) != 0)
+ bb.set(i);
+ else
+ bb.clear(i);
+ System.out.println("byte value: " + bt);
+ printBitSet(bb);
+
+ short st = (short)rand.nextInt();
+ BitSet bs = new BitSet();
+ for(int i = 15; i >= 0; i--)
+ if(((1 << i) & st) != 0)
+ bs.set(i);
+ else
+ bs.clear(i);
+ System.out.println("short value: " + st);
+ printBitSet(bs);
+
+ int it = rand.nextInt();
+ BitSet bi = new BitSet();
+ for(int i = 31; i >= 0; i--)
+ if(((1 << i) & it) != 0)
+ bi.set(i);
+ else
+ bi.clear(i);
+ System.out.println("int value: " + it);
+ printBitSet(bi);
+
+ // Test bitsets >= 64 bits:
+ BitSet b127 = new BitSet();
+ b127.set(127);
+ System.out.println("set bit 127: " + b127);
+ BitSet b255 = new BitSet(65);
+ b255.set(255);
+ System.out.println("set bit 255: " + b255);
+ BitSet b1023 = new BitSet(512);
+ b1023.set(1023);
+ b1023.set(1024);
+ System.out.println("set bit 1023: " + b1023);
+ }
+}
+/* Output:
+byte value: -107
+bits: {0, 2, 4, 7}
+bit pattern: 101010010000000000000000000000000000000000
+0000000000000000000000
+short value: 1302
+bits: {1, 2, 4, 8, 10}
+bit pattern: 011010001010000000000000000000000000000000
+0000000000000000000000
+int value: -2014573909
+bits: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24,
+25, 26, 31}
+bit pattern: 110101010101000000110111111000010000000000
+0000000000000000000000
+set bit 127: {127}
+set bit 255: {255}
+set bit 1023: {1023, 1024}
+*/
+```
+
+随机数生成器用于创建随机 **byte** , **short** 和 **int** ,并且每个都在 **BitSet** 中转换为相应的位模式。这样可以正常工作,因为 **BitSet** 是64位,所以这些都不会导致它的大小增加,然后创建更大的 **BitSet** 。 请注意, **BitSet** 会根据需要进行扩展。
+对于可以命名的固定标志集, **EnumSet** (参见[第二十二章:枚举]()章节)通常比 **BitSet** 更好,因为 **EnumSet** 允许操作名称而不是数字位位置,从而可以减少错误。 **EnumSet** 还可以防止意外地添加新的标记位置,这可能会导致一些严重的,难以发现的错误。使用 **BitSet** 而不是 **EnumSet** 的唯一原因是,不知道在运行时需要多少标志,或者为标志分配名称是不合理的,或者需要 **BitSet** 中的一个特殊操作(请参阅 **BitSet** 和 **EnumSet** 的 JDK 文档)。
## 本章小结
+集合可以说是编程语言中最常用的工具。有些语言(例如Python)甚至将基本集合组件(列表,映射和集合)作为内置函数包含在其中。
+正如在[第十二章:集合]()章节中看到的那样,可以使用集合执行许多非常有用的操作,而不需要太多努力。但是,在某些时候,为了正确地使用它们而不得不更多地了解集合,特别是,必须充分了解散列操作以编写自己的 `hashCode()` 方法(并且必须知道何时需要),并且你必须充分了解各种集合实现,以根据你的需求选择合适的集合。本附录涵盖了这些概念,并讨论了有关集合库的其他有用详细信息。你现在应该已经准备好在日常编程任务中使用 Java 集合了。
+集合库的设计很困难(大多数库设计问题都是如此)。在 C++ 中,集合类涵盖了许多不同类的基础。这比之前可用的 C++ 集合类更好,但它没有很好地转换为 Java 。在另一个极端,我看到了一个由单个类“collection”组成的集合库,它同时充当线性序列和关联数组。 Java 集合库试图在功能和复杂性之间取得平衡。结果在某些地方看起来有点奇怪。与早期 Java 库中的一些决策不同,这些奇怪的不是事故,而是在基于复杂性的权衡下而仔细考虑的决策。
[^1]: **java.util** 中的 **Map** 使用 **Map** 的 `getKey()` 和 `getValue()` 执行批量复制,因此这是有效的。如果自定义 **Map** 只是复制整个 **Map.Entry** ,那么这种方法就会出现问题。
From b9088f8412fb1dab9f2198a02554aa57e9c11792 Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Wed, 31 Jul 2019 16:46:46 +0800
Subject: [PATCH 011/475] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index db7bce50..fdd8b68c 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@
- [ ] [附录:标准IO](docs/book/Appendix-Standard-IO.md)
- [x] [附录:新IO](docs/book/Appendix-New-IO.md)
- [ ] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md)
-- [ ] [附录:集合主题](docs/book/Appendix-Collection-Topics.md)
+- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md)
- [ ] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md)
- [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md)
- [ ] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md)
From 92061f0594631cf2fba2eef1790229ad6d1a13f2 Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Wed, 31 Jul 2019 16:46:48 +0800
Subject: [PATCH 012/475] Update SUMMARY.md
---
SUMMARY.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SUMMARY.md b/SUMMARY.md
index 70f89c02..eea7c550 100644
--- a/SUMMARY.md
+++ b/SUMMARY.md
@@ -377,7 +377,7 @@
* [理解Map](docs/book/Appendix-Collection-Topics.md#理解Map)
* [集合工具类](docs/book/Appendix-Collection-Topics.md#集合工具类)
* [持有引用](docs/book/Appendix-Collection-Topics.md#持有引用)
- * [避免旧式类库](docs/book/Appendix-Collection-Topics.md#避免旧式类库)
+ * [Java 1.0 / 1.1 的集合类](docs/book/Appendix-Collection-Topics.md#避免旧式类库)
* [本章小结](docs/book/Appendix-Collection-Topics.md#本章小结)
* [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md)
* [线程](docs/book/Appendix-Low-Level-Concurrency.md#线程)
From 055ea338f61c7f4eff1cd77f0ce14e4576af3532 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Wed, 31 Jul 2019 16:55:56 +0800
Subject: [PATCH 013/475] =?UTF-8?q?=E5=B0=86=E2=80=9Dbase=20class=E2=80=9C?=
=?UTF-8?q?=E5=92=8C=E2=80=9Dbase=20type=E2=80=9C=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E7=BF=BB=E8=AF=91=E4=B8=BA=E2=80=9D=E5=9F=BA=E7=B1=BB=E2=80=9C?=
=?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=94=B9=E4=BA=86=E4=B8=80=E4=BA=9B=E5=B0=8F?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/01-What-is-an-Object.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md
index 3a3f0177..ace67d37 100644
--- a/docs/book/01-What-is-an-Object.md
+++ b/docs/book/01-What-is-an-Object.md
@@ -116,17 +116,17 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。
-通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基础类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。
+通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。

-这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基本类型和派生类型的概念来表达这种相似性。基本类型包含派生自它的类型之间共享的所有特征和行为。创建基本类型以表示思想的核心。从基类型中派生出其他类型来表示实现该核心的不同方式。
+这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。

-例如,垃圾回收机对垃圾进行分类。基本类型是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)衍生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基本类型是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以衍生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。
+例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。

@@ -157,7 +157,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 例如,你可以通过通用的“形状”基类派生出新的“五角形”形状的子类,而不需要修改通用"形状"基类的方法。通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。
-这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基础类型(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。
+这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。
如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?举个例子,下图的 **BirdController** 对象和通用 **Bird** 对象中,**BirdController** 不知道 **Bird** 的确切类型却还能一起工作。从 **BirdController** 的角度来看,这是很方便的,因为它不需要编写特别的代码来确定 **Bird** 对象的确切类型或行为。那么,在调用 **move()** 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?
@@ -213,19 +213,19 @@ void doSomething(Shape shape) {
shape.draw();
```
-我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做…...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()` 你自己,去做吧,注意细节。”
+我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做…...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()`,你自己去做吧,注意细节。”
尽管我们没作出任何特殊指示,程序的操作也是完全正确和恰当的。我们知道,为 Circle 调用`draw()` 时执行的代码与为一个 Square 或 Line 调用 `draw()` 时执行的代码是不同的。但在将 `draw()` 信息发给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确的操作。这非常神奇,因为当 Java 编译器为 `doSomething()` 编译代码时,它并不知道自己要操作的准确类型是什么。
-尽管我们确实可以保证最终会为 Shape 调用 `erase()`、 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?
+尽管我们确实可以保证最终会为 Shape 调用 `erase()` 和 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?
发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。
## 单继承结构
-自从 C++ 引入以来,一个OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终的基类的名字就是 `Object`。
+自从 C++ 引入以来,一个OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。
-Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基本类型。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。
+Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。
对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。
@@ -244,7 +244,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共
还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。
-在一些库中,一两个集合泛型集合就能满足我们所有的需求了,而在其他(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、堆(Stack)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:
+在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:
1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。
From 9884846706ab19c5aafbf376e63336554bac566e Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Wed, 31 Jul 2019 22:22:29 +0800
Subject: [PATCH 014/475] =?UTF-8?q?[revision=2013](=E6=88=AA=E8=87=B3=20?=
=?UTF-8?q?=E5=A4=9A=E5=8F=82=E6=95=B0=E5=87=BD=E6=95=B0=E5=BC=8F=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/13-Functional-Programming.md | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index 878906b2..6a7eabeb 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -630,7 +630,7 @@ public class FunctionalAnnotation {
`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的值在 `NotFunctional` 的定义中可见:接口中如果有多个方法则会产生编译时错误消息。
-仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。
+仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。
尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java 不允许我们将 `FunctionalAnnotation` 像 `fac` 定义一样直接赋值给 `Functional`,因为它没有明确地实现 `Functional` 接口。 令人惊奇的是 ,Java 8 允许我们以简便的语法为接口赋值函数。
@@ -638,17 +638,17 @@ public class FunctionalAnnotation {
以下是基本命名准则:
-1. 如果只处理对象而非基本类型,名称为 `Function`,`Consumer`,`Predicate` 等。参数类型通过泛型添加。
+1. 如果只处理对象而非基本类型,名称则为 `Function`,`Consumer`,`Predicate` 等。参数类型通过泛型添加。
-2. 如果是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但基本 `Supplier` 类型例外。
+2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但基本 `Supplier` 类型例外。
3. 如果返回值为基本类型,则用 `To` 表示,如 `ToLongFunction ` 和 `IntToLongFunction`。
4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。
-5. 如果是 2 个参数且返回值为布尔值,则是一个谓词(Predicate)。
+5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
-6. 如果是两个参数类型不同,则名称中有一个 `Bi`。
+6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。
下表描述了 `java.util.function` 中的目标类型(包括例外情况):
@@ -669,15 +669,15 @@ public class FunctionalAnnotation {
|2 参数类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**|
- 此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出更多行的函数式接口。
+此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出更多行的函数式接口。
-能看出来在创建 `java.util.function` 时,设计者做出了一些选择。
+可以看出,在创建 `java.util.function` 时,设计者们做出了一些选择。
-例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我对他们放弃的原因表示同情)。这是疏忽还是有人决定(他们是如何得出这个结论的)?
+例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我对他们放弃的原因表示同情)。这些选择是疏忽还是有人认为其他组合的使用情况出现得很少(他们是如何得出这个结论的)?
-你还可以看到基本类型给 Java 添加了多少复杂性。为了缓解效率问题,该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然受到语言设计选择不佳的影响。
+你还可以看到基本类型给 Java 添加了多少复杂性。为了缓和效率问题,该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然受到语言设计选择不佳的影响。
-下面枚举基于 Lambda 表达式的所有不同 Function 变体的示例:
+下面枚举了基于 Lambda 表达式的所有不同 **Function** 变体的示例:
```java
// functional/FunctionVariants.java
@@ -745,7 +745,7 @@ public class FunctionVariants {
}
```
-这些 Lambda 表达式尝试生成适合对应函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。
+这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。
主方法中的每个测试都显示了 `Function` 接口中不同类型的 `apply()` 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。
@@ -788,7 +788,7 @@ someOtherName()
查看 `BiConsumer` 的文档,你会看到 `accept()` 方法。 实际上,如果我们将方法命名为 `accept()`,它就可以作为方法引用。 但是我们也可用不同的名称,比如 `someOtherName()`。只要参数类型、返回类型与 `BiConsumer` 的 `accept()` 相同即可。
-因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法(在本例中为 `accept()`),而不是你的方法名。
+因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 `accept()`),而不是你的方法名。
现在我们来看看所有基于类的函数式,应用于方法引用(即那些不涉及基本类型的函数)。下例我们创建了一个最简单的函数式签名。代码示例:
From 71512c65c875c047941cd7117dc053a3568e6b28 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Thu, 1 Aug 2019 12:02:01 +0800
Subject: [PATCH 015/475] =?UTF-8?q?=E5=B0=86=20High-Precision=20Numbers=20?=
=?UTF-8?q?=E7=9A=84=E7=BF=BB=E8=AF=91=E4=BB=8E=E2=80=9D=E9=AB=98=E7=B2=BE?=
=?UTF-8?q?=E6=95=B0=E5=80=BC=E7=9A=84=E5=AD=98=E5=82=A8=E2=80=9C=E6=94=B9?=
=?UTF-8?q?=E4=B8=BA=E2=80=9D=E9=AB=98=E7=B2=BE=E5=BA=A6=E6=95=B0=E5=80=BC?=
=?UTF-8?q?=E2=80=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/03-Objects-Everywhere.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md
index ee8f7dd9..2941c371 100644
--- a/docs/book/03-Objects-Everywhere.md
+++ b/docs/book/03-Objects-Everywhere.md
@@ -100,7 +100,7 @@ char c = ch;
-### 高精数值的存储
+### 高精度数值
在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。
@@ -641,4 +641,4 @@ class AllTheColorsOfTheRainbow {
[^10]: 为了保持本书的代码排版紧凑,我并没完全遵守规范,但我尽量会做到符合 Java 标准。
-
\ No newline at end of file
+
From 197ac68ec18c67f724114471670833365fe9d878 Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Thu, 1 Aug 2019 20:11:35 +0800
Subject: [PATCH 016/475] =?UTF-8?q?=E6=A0=A1=E8=AE=A2=20=E7=AC=AC=E5=8D=81?=
=?UTF-8?q?=E4=BA=8C=E7=AB=A0=EF=BC=9A=E9=9B=86=E5=90=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/12-Collections.md | 173 ++++++++++++++++++------------------
1 file changed, 86 insertions(+), 87 deletions(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index d95b3c81..44872d57 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -22,9 +22,9 @@ MyType aReference;
## 泛型和类型安全的集合
-使用 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”类型的警告([注解]()章节将介绍更多有关注解的信息):
+在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 `@SuppressWarning` 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息):
```java
// collections/ApplesAndOrangesWithoutGenerics.java
@@ -64,11 +64,11 @@ ndOrangesWithoutGenerics.java:23)
*/
```
-**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 **add()** 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 **get()** 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 **id()** 方法之前,强制执行转型。否则,将会产生语法错误。
+**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 `add()` 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 `get()` 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 `id()` 方法之前,强制执行转型。否则,将会产生语法错误。
在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。
-在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
+在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:
```java
@@ -94,7 +94,7 @@ public class ApplesAndOrangesWithGenerics {
*/
```
-在 **apples** 定义的右侧,可以看到 **new ArrayList<>()** 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
+在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
```java
ArrayList apples = new ArrayList();
@@ -104,7 +104,7 @@ ArrayList apples = new ArrayList();
有了 **ArrayList** 声明中的类型指定,编译器会阻止将 **Orange** 放入 **apples** ,因此,这会成为一个编译期错误而不是运行时错误。
-使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 **get()** 时,它会替你执行转型。这样,不仅可以知道编译器将会检查放入集合中的对象的类型,而且在使用集合中的对象时,可以使用更加清晰的语法。
+使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。这样,不仅可以知道编译器将会检查放入集合中的对象的类型,而且在使用集合中的对象时,可以使用更加清晰的语法。
当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:
```java
@@ -137,7 +137,7 @@ Braeburn@4e25154f
因此,可以将 **Apple** 的子类型添加到被指定为保存 **Apple** 对象的集合中。
-程序的输出是从 **Object** 默认的 **toString()** 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 **hashCode()** 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。
+程序的输出是从 **Object** 默认的 `toString()` 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 `hashCode()` 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。
## 基本概念
@@ -145,7 +145,7 @@ Braeburn@4e25154f
Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:
1. **集合(Collection)** :一个独立元素的序列,这些元素都服从一条或多条规则。**List** 必须以插入的顺序保存元素, **Set** 不能包含重复元素, **Queue** 按照*排队规则*来确定对象产生的顺序(通常与它们被插入的顺序相同)。
-2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map**允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map**s是强大的编程工具。
+2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map** 允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map** 是强大的编程工具。
尽管并非总是可行,但在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 **List** :
@@ -182,9 +182,9 @@ public class SimpleCollection {
*/
```
-这个例子仅适用 **Collection** 方法,所以任何继承自 **Collection** 的类的对象都可以使用。但是 **ArrayList** 是最基本的序列类型。
+这个例子仅使用了 **Collection** 中的方法(即 `add()` ),所以使用任何继承自 **Collection** 的类的对象都可以正常工作。但是 **ArrayList** 是最基本的序列类型。
-**add()** 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 **add()** “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,**add()** 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。
+`add()` 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 `add()` “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,`add()` 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。
可以使用 *for-in* 语法来遍历所有的 **Collection** ,就像这里所展示的那样。在本章的后续部分,还将学习到一个更灵活的概念,*迭代器*。
@@ -194,7 +194,7 @@ public class SimpleCollection {
在 **java.util** 包中的 **Arrays** 和 **Collections** 类中都有很多实用的方法,可以在一个 **Collection** 中添加一组元素。
-**Arrays.asList()** 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 **Collections.addAll()** 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 **addAll()** 方法,所有 **Collection** 类型都包含该方法:
+`Arrays.asList()` 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 `Collections.addAll()` 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 `addAll()` 方法,所有 **Collection** 类型都包含该方法:
```java
// collections/AddingGroups.java
@@ -220,11 +220,11 @@ public class AddingGroups {
}
```
-**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 **Arrays.asList()** 来为这个构造器产生输入。但是, **Collections.addAll()** 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 **Collections.addAll()** ,因此这是首选方式。
+**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 `Arrays.asList()` 来为这个构造器产生输入。但是, `Collections.addAll()` 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 `Collections.addAll()` ,因此这是首选方式。
-**Collection.addAll()** 方法只能接受另一个 **Collection** 作为参数,因此它没有 **Arrays.asList()** 或 **Collections.addAll()** 灵活。这两个方法都使用可变参数列表。
+`Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。
-也可以直接使用 **Arrays.asList()** 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 **add()** 或 **delete()**,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
+也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `delete()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
```java
// collections/AsListInference.java
@@ -260,12 +260,12 @@ public class AsListInference {
}
```
-在 **snow4** 中,注意 **Arrays.asList()** 中间的“hint”,告诉编译器 **Arrays.asList()** 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。
+在 **snow4** 中,注意 `Arrays.asList()` 中间的“暗示”(即 `` ),告诉编译器 `Arrays.asList()` 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。
## 集合的打印
-必须使用 **Arrays.toString()** 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:
+必须使用 `Arrays.toString()` 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:
```java
// collections/PrintingCollections.java
// Collections print themselves automatically
@@ -310,24 +310,23 @@ public class PrintingCollections {
*/
```
-这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List**, 它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。
+这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。
默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。
-第一个 **fill()** 方法适用于所有类型的 **Collection** ,这些类型都实现了 **add()** 方法以添加新元素。
+第一个 `fill()` 方法适用于所有类型的 **Collection** ,这些类型都实现了 `add()` 方法以添加新元素。
**ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。
**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。
-**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。 这是由于这种行为,对于每个键, **Map** 只存储一次。
-
-**Map.put(key, value)** 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 **Map.get(key)** 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。
+**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。
+`Map.put(key, value)` 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 `Map.get(key)` 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。
请注意,这里没有指定(或考虑) **Map** 的大小,因为它会自动调整大小。 此外, **Map** 还知道如何打印自己,它会显示相关联的键和值。
-本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap**和 **LinkedHashMap** 。
+本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。
键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。
@@ -345,7 +344,7 @@ public class PrintingCollections {
下面的示例导入 **typeinfo.pets** ,超前使用了[类型信息]()一章中的类库。这个类库包含了 **Pet** 类层次结构,以及用于随机生成 **Pet** 对象的一些工具类。此时不需要了解完整的详细信息,只需要知道两点:
1. 有一个 **Pet** 类,以及 **Pet** 的各种子类型。
-2. 静态的 **Pets.arrayList()** 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:
+2. 静态的 `Pets.arrayList()` 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:
```java
// collections/ListFeatures.java
@@ -442,44 +441,44 @@ sub: [Mouse, Pug]
打印行都编了号,因此可从输出追溯到源代码。 第 1 行输出展示了原始的由 **Pet** 组成的 **List** 。 与数组不同, **List** 可以在创建后添加或删除元素,并自行调整大小。这正是它的重要价值:一种可修改的序列。在第 2 行输出中可以看到添加一个 **Hamster** 的结果,该对象将被追加到列表的末尾。
-可以使用 **contains()** 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 **remove()** 方法。同样,如果有一个对象的引用,可以使用 **indexOf()** 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。
+可以使用 `contains()` 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 `remove()` 方法。同样,如果有一个对象的引用,可以使用 `indexOf()` 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。
-当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 **equals()** 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 **indexOf()** 方法,结果仍为 **-1** (表示未找到),并且尝试调用 **remove()** 方法来删除这个对象将返回 **false** 。对于其他类, **equals()** 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 **equals()** 行为而发生变化。
+当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 `equals()` 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 `indexOf()` 方法,结果仍为 **-1** (表示未找到),并且尝试调用 `remove()` 方法来删除这个对象将返回 **false** 。对于其他类, `equals()` 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 `equals()` 行为而发生变化。
第 7、8 行输出展示了删除与 **List** 中的对象完全匹配的对象是成功的。
-可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间进执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。
+可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。
-**subList()** 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 **containsAll()** 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 **Collections.sort()** 和 **Collections.shuffle()** 方法,不会影响 **containsAll()** 的结果。 **subList()** 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。
+`subList()` 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 `containsAll()` 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 `Collections.sort()` 和 `Collections.shuffle()` 方法,不会影响 `containsAll()` 的结果。 `subList()` 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。
-**retainAll()** 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 **equals()** 方法。
+`retainAll()` 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 `equals()` 方法。
-第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 **equals()** 的行为。
+第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 `equals()` 的行为。
-**removeAll()** 方法也是基于 **equals()** 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。
+`removeAll()` 方法也是基于 `equals()` 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。
-**set()** 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
+`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
-第 17 行输出表明,对于 **List** ,有一个重载的 **addAll()** 方法可以将新列表插入到原始列表的中间,而不仅仅只能用 **Collection** 的 **addAll()** 方法将追加到列表末尾。
+第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间,而不仅仅只能用 **Collection** 的 `addAll()` 方法将追加到列表末尾。
-第 18 - 20 行输出展示了 **isEmpty()** 和 **clear()** 方法的效果。
+第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。
-第 22、23 行输出展示了如何使用 **toArray()** 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 **toArray()** 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 **id()** 方法,可以在所产生的数组中的对象上调用这个方法。
+第 22、23 行输出展示了如何使用 `toArray()` 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 `toArray()` 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 `id()` 方法,可以在所产生的数组中的对象上调用这个方法。
## 迭代器Iterators
-在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , **add()** 是插入元素的一种方式, **get()** 是获取元素的一种方式。
+在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , `add()` 是插入元素的一种方式, `get()` 是获取元素的一种方式。
如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 **List** 编码的,但是后来发现如果能够将相同的代码应用于 **Set** 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?
-*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:
+*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:
-1. 使用 **iterator()** 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。
-2. 使用 **next()** 方法获得序列中的下一个元素。
-3. 使用 **hasNext()** 方法检查序列中是否还有元素。
-4. 使用 **remove()** 方法将迭代器最近返回的那个元素删除。
+1. 使用 `iterator()` 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。
+2. 使用 `next()` 方法获得序列中的下一个元素。
+3. 使用 `hasNext()` 方法检查序列中是否还有元素。
+4. 使用 `remove()` 方法将迭代器最近返回的那个元素删除。
为了观察它的工作方式,这里再次使用[类型信息]()章节中的 **Pet** 工具:
@@ -519,15 +518,15 @@ public class SimpleIteration {
*/
```
-有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 **hasNext()** 和 **next()** 关心的事情。
+有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 `hasNext()` 和 `next()` 关心的事情。
如果只是想向前遍历 **List** ,并不打算修改 **List** 对象本身,那么使用 *for-in* 语法更加简洁。
-**Iterator** 还可以删除由 **next()** 生成的最后一个元素,这意味着在调用 **remove()** 之前必须先调用 **next()** 。[^4]
+**Iterator** 还可以删除由 `next()` 生成的最后一个元素,这意味着在调用 `remove()` 之前必须先调用 `next()` 。[^4]
在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。
-现在考虑创建一个 **display()** 方法,它不必知晓集合的确切类型:
+现在考虑创建一个 `display()` 方法,它不必知晓集合的确切类型:
```java
// collections/CrossCollectionIteration.java
@@ -565,7 +564,7 @@ public class CrossCollectionIteration {
*/
```
-**display()** 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。
+`display()` 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。
我们可以使用 **Iterable** 接口生成上一个示例的更简洁版本,该接口描述了“可以产生 **Iterator** 的任何东西”:
@@ -606,12 +605,12 @@ public class CrossCollectionIteration2 {
*/
```
-这里所有的类都是 **Iterable** ,所以现在对 **display()** 的调用显然更简单。
+这里所有的类都是 **Iterable** ,所以现在对 `display()` 的调用显然更简单。
### ListIterator
-**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。虽然 **Iterator** 只能向前移动,但 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 **set()** 方法替换它访问过的最后一个元素。可以通过调用 **listIterator()** 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 **listIterator(n)** 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:
+**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。 **Iterator** 只能向前移动,而 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 `set()` 方法替换它访问过的最近一个元素。可以通过调用 `listIterator()` 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 `listIterator(n)` 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:
```java
// collections/ListIteration.java
@@ -650,7 +649,7 @@ EgyptianMau]
*/
```
-**Pets.get()** 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。
+`Pets.get()` 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。
@@ -660,11 +659,11 @@ EgyptianMau]
**LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque)** 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 **Queue** 中)。例如:
-- **getFirst()** 和 **element()** 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 **peek()** 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。
-- **removeFirst()** 和 **remove()** 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 **poll()** 稍有差异,它在列表为空时返回 **null** 。
-- **addFirst()** 在列表的开头插入一个元素。
-- **offer()** 与 **add()** 和 **addLast()** 相同。 它们都在列表的尾部(末尾)添加一个元素。
-- **removeLast()** 删除并返回列表的最后一个元素。
+- `getFirst()` 和 `element()` 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 `peek()` 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。
+- `removeFirst()` 和 `remove()` 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 `poll()` 稍有差异,它在列表为空时返回 **null** 。
+- `addFirst()` 在列表的开头插入一个元素。
+- `offer()` 与 `add()` 和 `addLast()` 相同。 它们都在列表的尾部(末尾)添加一个元素。
+- `removeLast()` 删除并返回列表的最后一个元素。
下面的示例展示了这些功能之间基本的相似性和差异性。它并不是重复执行 **ListFeatures.java** 中所示的行为:
@@ -722,7 +721,7 @@ pets.removeLast(): Hamster
*/
```
-**Pets.list()** 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 **element()** , **offer()** , **peek()** , **poll()** 和 **remove()** 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。
+`Pets.list()` 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 `element()` , `offer()` , `peek()` , `poll()` 和 `remove()` 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。
@@ -772,7 +771,7 @@ public class Stack {
}
```
-这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, **push()** 接受类型为 **T** 的对象,而 **peek()** 和 **pop()** 返回类型为 **T** 的对象。 **peek()** 方法将返回栈顶元素,但并不将其从栈顶删除,而 **pop()** 删除并返回顶部元素。
+这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **\** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。
如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。
@@ -837,7 +836,7 @@ import onjava.Stack;
**Set** 不保存重复的元素。 如果试图将相同对象的多个实例添加到 **Set** 中,那么它会阻止这种重复行为。 **Set** 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 **Set** 中。因此,查找通常是 **Set** 最重要的操作,因此通常会选择 **HashSet** 实现,该实现针对快速查找进行了优化。
-**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的问题将在[附录:集合主题]()中介绍。
+**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的内容将在[附录:集合主题]()中介绍。
下面是使用存放 **Integer** 对象的 **HashSet** 的示例:
@@ -862,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** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 **Integer** 按顺序排序。但是,您不应该依赖此行为:
```java
// collections/SetOfString.java
@@ -916,7 +915,7 @@ public class SortedSetOfString {
*/
```
-最常见的操作之一是使用 **contains()** 测试成员归属性,但也有一些其它操作,可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):
+最常见的操作之一是使用 `contains()` 测试成员归属性,但也有一些其它操作,这可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):
```java
// collections/SetOperations.java
@@ -958,9 +957,9 @@ Z]
*/
```
-这些方法名都是自解释的,JDK文档中还有一些其它的方法。
+这些方法名都是自解释的,JDK 文档中还有一些其它的方法。
-能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 **java.nio.file.Files.readAllLines()** 方法,可以打开一个文件,并将其作为一个 **List\** 读取,每个 **String** 都是输入文件中的一行:
+能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 `java.nio.file.Files.readAllLines()` 方法,可以打开一个文件,并将其作为一个 **List\** 读取,每个 **String** 都是输入文件中的一行:
```java
// collections/UniqueWords.java
@@ -991,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
@@ -1058,9 +1057,9 @@ public class Statistics {
*/
```
-- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 **get()** 返回 **null** (这意味着这是第一次找到该数字)。否则, **get()** 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。
+- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 `get()` 返回 **null** (这意味着这是第一次找到该数字)。否则, `get()` 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。
-接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 **containsKey()** 和 **containsValue()** 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:
+接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 `containsKey()` 和 `containsValue()` 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:
```java
// collections/PetMap.java
@@ -1089,7 +1088,7 @@ true
*/
```
-**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\\>** 即可:
+**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\>** 即可:
```java
@@ -1157,7 +1156,7 @@ Person Luke has:
*/
```
-**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 **keySet()** 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。
+**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 `keySet()` 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。
@@ -1196,9 +1195,9 @@ 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()` 都返回队头元素而不删除它,但是如果队列为空,则 `element()` 抛出 **NoSuchElementException** ,而 `peek()` 返回 **null** 。 `poll()` 和 `remove()`* 都删除并返回队头元素,但如果队列为空,`poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。
-自动包装机制会自动将 **nextInt()** 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。
+自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。
与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。
@@ -1209,7 +1208,7 @@ B r o n t o s a u r u s
优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。
-当在 **PriorityQueue** 上调用 **offer()** 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用**peek()** , **poll()** 或 **remove()** 方法时,获得的元素将是队列中优先级最高的元素。
+当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。
让 **PriorityQueue** 与 **Integer** , **String** 和 **Character** 这样的内置类型一起工作易如反掌。在下面的示例中,第一组值与前一个示例中的随机值相同,可以看到它们从 **PriorityQueue** 中弹出的顺序与前一个示例不同:
@@ -1266,7 +1265,7 @@ C B A A
*/
```
-**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\** 构造器的调用,和第二个对 **PriorityQueue\** 的调用使用了由 **Collections.reverseOrder()** (Java 5 中新添加的)产生的反序的 **Comparator** 。
+**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\** 构造器的调用,和第二个对 **PriorityQueue\** 的调用使用了由 `Collections.reverseOrder()` (Java 5 中新添加的)产生的反序的 **Comparator** 。
最后一部分添加了一个 **HashSet** 来消除重复的 **Character**。
@@ -1277,7 +1276,7 @@ C B A A
**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
@@ -1334,11 +1333,11 @@ Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}
*/
```
-两个版本的 **display()** 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 **display()** 方法与低层集合的特定实现解耦。
+两个版本的 `display()` 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 `display()` 方法与低层集合的特定实现解耦。
-在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 **display(Collection)** 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。
+在本例中,这两种方式都可以奏效。事实上, **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** 而很容易地实现,但是无论如何还是要被强制去实现 `iterator()` 和 `size()` 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到:
```java
// collections/CollectionSequence.java
@@ -1380,11 +1379,11 @@ extends AbstractCollection {
*/
```
-**remove()** 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。
+`remove()` 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。
-- **[1]** 你可能会认为,因为 **iterator()** 返回 **Iterator\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。
+- **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。
-这个例子表明,如果实现了 **Collection** ,就必须实现 **iterator()** ,并且只拿实现 **iterator()** 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。 在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。 此时,继承并提供创建迭代器的能力要容易得多:
+这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多:
```java
// collections/NonCollectionSequence.java
@@ -1451,7 +1450,7 @@ public class ForInCollections {
由于 **cs** 是一个 **Collection** ,因此该代码展示了使用 *for-in* 是所有 **Collection** 对象的特征。
-这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 **iterator()** 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:
+这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 `iterator()` 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:
```java
// collections/IterableClass.java
@@ -1488,7 +1487,7 @@ And that is how we know the Earth to be banana-shaped.
*/
```
-**iterator()** 返回的是实现了 **Iterator\** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在 **main()** 中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。
+`iterator()` 返回的是实现了 **Iterator\** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在主方法中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。
在 Java 5 中,许多类都是 **Iterable** ,主要包括所有的 **Collection** 类(但不包括各种 **Maps** )。 例如,下面的代码可以显示所有的操作系统环境变量:
@@ -1507,7 +1506,7 @@ public class EnvironmentVariables {
}
```
-**System.getenv()** [^7]返回一个 **Map** , **entrySet()** 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。
+`System.getenv()` [^7]返回一个 **Map** , `entrySet()` 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。
*for-in* 语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱:
@@ -1539,7 +1538,7 @@ public class ArrayIsNotIterable {
### 适配器方法惯用法
-如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢? 例如,假设你希望可以选择是在正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 **iterator()** 方法,则只能替换现有的方法,而不能实现选择。
+如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢? 例如,假设你希望可以选择是在正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现选择。
一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。
在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:
@@ -1592,7 +1591,7 @@ be to not or be To
*/
```
-在 **main()** 中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 **reversed()** 方法,它会产生不同的行为。
+在主方法中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 `reversed()` 方法,它会产生不同的行为。
通过使用这种方式,可以在 **IterableClass.java** 示例中添加两种适配器方法:
@@ -1649,9 +1648,9 @@ And that is how we know the Earth to be banana-shaped.
*/
```
-注意,第二个方法 **random()** 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。
+注意,第二个方法 `random()` 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。
-从输出中可以看到, **Collections.shuffle()** 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 **randomized()** 方法用一个 **ArrayList** 将 **Arrays.asList()** 的结果包装了起来。如果这个由 **Arrays.asList()** 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:
+从输出中可以看到, `Collections.shuffle()` 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 `randomized()` 方法用一个 **ArrayList** 将 `Arrays.asList()` 的结果包装了起来。如果这个由 `Arrays.asList()` 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:
```java
// collections/ModifyingArraysAsList.java
@@ -1685,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** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。
## 本章小结
@@ -1791,7 +1790,7 @@ Serializable]
*/
```
-除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 **entrySet()** 和 **values()** 方法来产生 **Collection** 。
+除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 `entrySet()` 和 `values()` 方法来产生 **Collection** 。
请注意,标记接口 **java.util.RandomAccess** 附加到了 **ArrayList** 上,但不附加到 **LinkedList** 上。这为根据特定 **List** 动态改变其行为的算法提供了信息。
@@ -1799,19 +1798,19 @@ Serializable]
尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面,但我想你很快就会找到自己的路子,来获得和使用这个类库中的类。
-[^1]: 许多语言,例如Perl,Python和Ruby,都有集合的本地支持。
+[^1]: 许多语言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。
[^2]: 这里是操作符重载的用武之地,C++和C#的集合类都使用操作符重载生成了更简洁的语法。
[^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。
-[^4]: **remove()** 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准Java库集合实现了 **remove()** ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。
+[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。
[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。
[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。
-[^7]: 这在Java 5之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在提供它这一事实表明,Java的设计者们更加务实了。
+[^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。
From cd438293a616c913a09219c3296c22d23cc8d1b9 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Fri, 2 Aug 2019 11:06:14 +0800
Subject: [PATCH 017/475] Update 03-Objects-Everywhere.md
---
docs/book/03-Objects-Everywhere.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md
index 2941c371..bd96286f 100644
--- a/docs/book/03-Objects-Everywhere.md
+++ b/docs/book/03-Objects-Everywhere.md
@@ -417,7 +417,7 @@ import java.util.*;
2. 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。
-**static** 关键字(从 C++ 采用)就符合上述两点要求。当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。 即使我们从未创建过该类的对象,也可以调用其静态方法或访问静态字段。相反,对于普通的非静态字段和方法,我们必须要先创建一个对象并使用该对象来访问字段或方法,因为非静态字段和方法必须与特定对象关联 [^6] 。
+**static** 关键字(从 C++ 采用)就符合上述两点要求。当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。 即使我们从未创建过该类的对象,也可以调用其静态方法或访问其静态字段。相反,对于普通的非静态字段和方法,我们必须要先创建一个对象并使用该对象来访问字段或方法,因为非静态字段和方法必须与特定对象关联 [^6] 。
一些面向对象的语言使用类数据(class data)和类方法(class method),表示静态数据和方法只是作为类,而不是类的某个特定对象而存在的。有时 Java 文献也使用这些术语。
@@ -436,7 +436,7 @@ StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
```
-`st1.i` 和 `st2.i` 指向同一块存储空间,因此它们的值都是 47。引用静态变量有两种方法。在前面的示例中,我们通过一个对象来去定位它,例如 `st2.i`。我们也可以通过类名直接引用它,这种方式对于非静态成员则不行:
+`st1.i` 和 `st2.i` 指向同一块存储空间,因此它们的值都是 47。引用静态变量有两种方法。在前面的示例中,我们通过一个对象来定位它,例如 `st2.i`。我们也可以通过类名直接引用它,这种方式对于非静态成员则不可行:
```java
StaticTest.i++;
@@ -444,7 +444,7 @@ StaticTest.i++;
`++` 运算符将会使变量结果 + 1。此时 `st1.i` 和 `st2.i` 的值都变成了 48。
-使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态属性或方法 [^7]。 代码示例:
+使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态属性或方法 [^7]。 代码示例:
```java
class Incrementable {
@@ -454,7 +454,7 @@ class Incrementable {
}
```
-上例中,`Incrementable` 的 `increment()` 方法通过 `++` 运算符将静态数据 `i` 加1。我们依然可以先实例化对象再调用该方法。 代码示例:
+上例中,`Incrementable` 的 `increment()` 方法通过 `++` 运算符将静态数据 `i` 加 1。我们依然可以先实例化对象再调用该方法。 代码示例:
```java
Incrementable sf = new Incrementable();
@@ -489,17 +489,17 @@ public class HelloDate {
```
-在这本书中,所有代码示例的第一行都是注释行,其中包含文件的路径信息(比如本章的目录名是 **objects**),后跟文件名。我的工具可以根据这些信息自动提取和测试书籍的代码,你也可以通过参考第一行注释轻松地在 Github 库中找到对应的代码示例。
+在本书中,所有代码示例的第一行都是注释行,其中包含文件的路径信息(比如本章的目录名是 **objects**),后跟文件名。我的工具可以根据这些信息自动提取和测试书籍的代码,你也可以通过参考第一行注释轻松地在 Github 库中找到对应的代码示例。
如果你想在代码中使用一些额外的类库,那么就必须在程序文件的开始处使用 **import** 关键字来导入它们。之所以说是额外的,因为有一些类库已经默认自动导入到每个文件里了。例如:`java.lang` 包。
现在打开你的浏览器在 [Oracle](https://www.oracle.com/) 上查看文档。如果你还没有从 [Oracle](https://www.oracle.com/) 网站上下载 JDK 文档,那现在就去 [^8] 。查看包列表,你会看到 Java 附带的所有不同的类库。
-选择 `java.lang` ,你会看到该库中所有类的列表。由于 `java.lang` 隐式包含在每个 Java代码文件中,因此这些类是自动可用的。`java.lang` 类库中没有 **Date** 类,所以我们必须导入其他的类库(即 Date 所在的类库)。如果你不清楚某个类所在的类库或者想查看类库中所有的类,那么可以在 Java 文档中选择 “Tree” 查看。
+选择 `java.lang`,你会看到该库中所有类的列表。由于 `java.lang` 隐式包含在每个 Java 代码文件中,因此这些类是自动可用的。`java.lang` 类库中没有 **Date** 类,所以我们必须导入其他的类库(即 Date 所在的类库)。如果你不清楚某个类所在的类库或者想查看类库中所有的类,那么可以在 Java 文档中选择 “Tree” 查看。
现在,我们可以找到 Java 附带的每个类。使用浏览器的“查找”功能查找 **Date**,搜索结果中将会列出 **java.util.Date**,我们就知道了 **Date** 在 **util** 库中,所以必须导入 **java.util.*** 才能使用 **Date**。
-如果你在文档中选择 **java.lang**,然后选择 **System**,你会看到 **System** 类中有几个字段,如果你选择了 **out**,你会发现它是一个静态的 **PrintStream** 对象。 所以,即使我们不使用 **new** 创建, **out** 对象就已经存在并可以使用。 **out** 对象可以执行的操作取决于它的类型: **PrintStream** ,其在文档中是一个超链接,如果单击该链接,我们将可以看到 **PrintStream** 对应的方法列表(更多详情,将在本书后面介绍)。 现在我们重点说的是 **println()** 这个方法。 它的作用是 “将信息输出到控制台,并以换行符结束”。既然如此,我们可以这样编码来输出信息到控制台。 代码示例:
+如果你在文档中选择 **java.lang**,然后选择 **System**,你会看到 **System** 类中有几个字段,如果你选择了 **out**,你会发现它是一个静态的 **PrintStream** 对象。 所以,即使我们不使用 **new** 创建, **out** 对象就已经存在并可以使用。 **out** 对象可以执行的操作取决于它的类型: **PrintStream** ,其在文档中是一个超链接,如果单击该链接,我们将可以看到 **PrintStream** 对应的方法列表(更多详情,将在本书后面介绍)。 现在我们重点说的是 **println()** 这个方法。 它的作用是 “将信息输出到控制台,并以换行符结束”。既然如此,我们可以这样编码来输出信息到控制台。 代码示例:
```java
System.out.println("A String of things");
From 577fce56e29681766c9aa14c0076d96cd73de90b Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Fri, 2 Aug 2019 15:40:30 +0800
Subject: [PATCH 018/475] [hotfix] fix a typo
---
docs/book/04-Operators.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index ebf19580..05ed80d5 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -1472,7 +1472,7 @@ public class AllOps {
}
```
-**注意** :**boolean** 类型的的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。
+**注意** :**boolean** 类型的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。
在 **char**,**byte** 和 **short** 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 **int** 类型的运算则不用转换,因为默认就是 **int** 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 **int** 型的范围,这种情况下结果会发生溢出。下面的代码示例:
From 6b3fcca2ba3eef7236b8a67465c7de71d73dca3d Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Fri, 2 Aug 2019 16:17:16 +0800
Subject: [PATCH 019/475] [hotfix] fix a typo
---
docs/book/05-Control-Flow.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md
index e2f0dcb3..9f5676e8 100644
--- a/docs/book/05-Control-Flow.md
+++ b/docs/book/05-Control-Flow.md
@@ -28,7 +28,7 @@ public class TrueFalse {
true false
```
-通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 **注意**:在 Java 中使用数值作为布尔值是非法的,即便这种操作在 C/C++ 中是被允许的(在这些语言中,“真”为非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,那么首先需要使用条件表达式来产生 **boolean** 类型的结果,例如 `if(a! = 0)`。
+通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 **注意**:在 Java 中使用数值作为布尔值是非法的,即便这种操作在 C/C++ 中是被允许的(在这些语言中,“真”为非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,那么首先需要使用条件表达式来产生 **boolean** 类型的结果,例如 `if(a != 0)`。
## if-else
From a96df2b6ba5b2e47e277db58fd24c96838d57639 Mon Sep 17 00:00:00 2001
From: jjm <1065658889@qq.com>
Date: Fri, 2 Aug 2019 20:40:59 +0800
Subject: [PATCH 020/475] =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E6=A6=82=E5=BF=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/25-Patterns.md | 94 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 93 insertions(+), 1 deletion(-)
diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md
index 9ae1f0d2..345e46dc 100644
--- a/docs/book/25-Patterns.md
+++ b/docs/book/25-Patterns.md
@@ -6,10 +6,102 @@
## 概念
+最初,你可以将模式视为解决特定类问题的一种特别巧妙且有深刻见解的方法。这就像前辈已经从所有角度去解决问题,并提出了最通用,最灵活的解决方案。问题可能是你之前看到并解决过的问题,但你的解决方案可能没有你在模式中体现的那种完整性。
+虽然它们被称为“设计模式”,但它们实际上并不与设计领域相关联。模式似乎与传统的分析、设计和实现的思维方式不同。相反,模式在程序中体现了一个完整的思想,因此它有时会出现在分析阶段或高级设计阶段。因为模式在代码中有一个直接的实现,所以你可能不会期望模式在低级设计或实现之前出现(而且通常在到达这些阶段之前,你不会意识到需要一个特定的模式)。
+
+模式的基本概念也可以看作是程序设计的基本概念:添加抽象层。当你抽象一些东西的时候,就像在剥离特定的细节,而这背后最重要的动机之一是:
+> **将易变的事物与不变的事物分开**
+
+另一种方法是,一旦你发现程序的某些部分可能因某种原因而发生变化,你要保持这些变化不会引起整个代码中其他变化。 如果代码更容易理解,那么维护起来会更容易。
+
+通常,开发一个优雅且易维护设计中最困难的部分是发现我称之为变化的载体(也就是最易改变的地方)。这意味着找到系统中最重要的变化,换而言之,找到变化会导致最严重后果的地方。一旦发现变化载体,就可以围绕构建设计的焦点。
+
+因此,设计模式的目标是隔离代码中的更改。 如果以这种方式去看,你已经在本书中看到了设计模式。 例如,继承可以被认为是一种设计模式(虽然是由编译器实现的)。它允许你表达所有具有相同接口的对象(即保持相同的行为)中的行为差异(这就是变化的部分)。组合也可以被视为一种模式,因为它允许你动态或静态地更改实现类的对象,从而改变类的工作方式。
+
+你还看到了设计模式中出现的另一种模式:迭代器(Java 1.0和1.1随意地将其称为枚举; Java 2 集合才使用Iterator)。当你逐个选择元素时并逐步处理,这会隐藏集合的特定实现。迭代器允许你编写通用代码,该代码对序列中的所有元素执行操作,而不考虑序列的构建方式。因此,你的通用代码可以与任何可以生成迭代器的集合一起使用。
+
+即使模式是非常有用的,但有些人断言:
+> **设计模式代表语言的失败。**
+
+这是一个非常重要的见解,因为一个模式在 C++ 有意义,可能在JAVA或者其他语言中就没有意义。出于这个原因,所以一个模式可能出现在设计模式书上,不意味着应用于你的编程语言是有用的。
+
+我认为“语言失败”这个观点是有道理的,但是我也认为这个观点过于简单化。如果你试图解决一个特定的问题,而你使用的语言没有直接提供支持你使用的技巧,你可以说这个是语言的失败。但是,你使用特定的技巧的频率的是多少呢?也许平衡是对的:当你使用特定的技巧的时候,你必须付出更多的努力,但是你又没有足够的理由去使得语言支持这个技术。另一方面,没有语言的支持,使用这种技术常常会很混乱,但是在语言支持下,你可能会改变编程方式(例如,Java 8流实现此目的)。
+
+### 单例模式
+也许单例模式是最简单的设计模式,它是一种提供一个且只有一个对象实例的方法。这在java库中使用,但是这有个更直接的示例:
+
+```java
+// patterns/SingletonPattern.java
+interface Resource {
+ int getValue();
+ void setValue(int x);
+}
+
+/*
+* 由于这不是从Cloneable基类继承而且没有添加可克隆性,
+* 因此将其设置为final可防止通过继承添加可克隆性。
+* 这也实现了线程安全的延迟初始化:
+*/
+final class Singleton {
+ private static final class ResourceImpl implements Resource {
+ private int i;
+ private ResourceImpl(int i) {
+ this.i = i;
+ }
+ public synchronized int getValue() {
+ return i;
+ }
+ public synchronized void setValue(int x) {
+ i = x;
+ }
+ }
+
+ private static class ResourceHolder {
+ private static Resource resource = new ResourceImpl(47);
+ }
+ public static Resource getResource() {
+ return ResourceHolder.resource;
+ }
+}
+
+public class SingletonPattern {
+ public static void main(String[] args) {
+ Resource r = Singleton.getResource();
+ System.out.println(r.getValue());
+ Resource s2 = Singleton.getResource();
+ s2.setValue(9);
+ System.out.println(r.getValue());
+ try {
+ // 不能这么做,会发生:compile-time error(编译时错误).
+ // Singleton s3 = (Singleton)s2.clone();
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+} /* Output: 47 9 */
+```
+创建单例的关键是防止客户端程序员直接创建对象。 在这里,这是通过在Singleton类中将Resource的实现作为私有类来实现的。
+
+此时,你将决定如何创建对象。在这里,它是按需创建的,在第一次访问的时候创建。 该对象是私有的,只能通过public getResource()方法访问。
+
+
+懒惰地创建对象的原因是它嵌套的私有类resourceHolder在首次引用之前不会加载(在getResource()中)。当Resource对象加载的时候,静态初始化块将被调用。由于JVM的工作方式,这种静态初始化是线程安全的。为保证线程安全,Resource中的getter和setter是同步的。
+
+### 模式分类
+
+“设计模式”一书讨论了23种不同的模式,分为以下三种类别(所有这些模式都围绕着可能变化的特定方面)。
+
+1. **创建型**:如何创建对象。 这通常涉及隔离对象创建的细节,这样你的代码就不依赖于具体的对象的类型,因此在添加新类型的对象时不会更改。单例模式(Singleton)被归类为创作模式,本章稍后你将看到Factory Method的示例。
+
+2. **构造型**:设计对象以满足特定的项目约束。它们处理对象与其他对象连接的方式,以确保系统中的更改不需要更改这些连接。
+
+3. **行为型**:处理程序中特定类型的操作的对象。这些封装要执行的过程,例如解释语言、实现请求、遍历序列(如在迭代器中)或实现算法。本章包含观察者和访问者模式的例子。
+
+《设计模式》一书中每个设计模式都有单独的一个章节,每个章节都有一个或者多个例子,通常使用C++,但有时也使用SmallTalk。 本章不重复设计模式中显示的所有模式,因为该书独立存在,应单独研究。 相反,你会看到一些示例,可以为你提供关于模式的理解以及它们如此重要的原因。
-## 构建型
+## 构建应用程序框架
From f06b850470c9b14ca0e7ebe6a671eaee12dd4ef7 Mon Sep 17 00:00:00 2001
From: AlfredAlan <854723360@mail.com>
Date: Sun, 4 Aug 2019 17:53:56 +0800
Subject: [PATCH 021/475] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E5=B9=B6?=
=?UTF-8?q?=E5=8F=91=E5=BA=95=E5=B1=82=E5=8E=9F=E7=90=86=20=E7=BF=BB?=
=?UTF-8?q?=E8=AF=91=E8=87=B3=20DelayQueue?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Low-Level-Concurrency.md | 141 ++++++++++++++++++--
1 file changed, 133 insertions(+), 8 deletions(-)
diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md
index f7ef3ecc..e7f701dc 100644
--- a/docs/book/Appendix-Low-Level-Concurrency.md
+++ b/docs/book/Appendix-Low-Level-Concurrency.md
@@ -547,13 +547,13 @@ public class EvenProducer extends IntGenerator {
417 not even!
*/
```
-* [1] 一个任务有可能在另外一个任务执行第一个对 **currentEvenValue** 的递增操作之后,但是没有执行第二个操作之前,调用 `next()` 方法。这将使这个值处于 “不恰当” 的状态。
+* [1] 一个任务有可能在另外一个任务执行第一个对 **currentEvenValue** 的自增操作之后,但是没有执行第二个操作之前,调用 `next()` 方法。这将使这个值处于 “不恰当” 的状态。
为了证明这是可能发生的, `EvenChecker.test()` 创建了一组 **EventChecker** 对象,以连续读取 **EvenProducer** 的输出并测试检查每个数值是否都是偶数。如果不是,就会报告错误,而程序也将关闭。
多线程程序的部分问题是,即使存在 bug ,如果失败的可能性很低,程序仍然可以正确显示。
-重要的是要注意到递增操作自身需要多个步骤,并且在递增过程中任务可能会被线程机制挂起 - 也就是说,在 Java 中,递增不是原子性的操作。因此,如果不保护任务,即使单纯的递增也不是线程安全的。
+重要的是要注意到自增操作自身需要多个步骤,并且在自增过程中任务可能会被线程机制挂起 - 也就是说,在 Java 中,自增不是原子性的操作。因此,如果不保护任务,即使单纯的自增也不是线程安全的。
该示例程序并不总是在第一次非偶数产生时终止。所有任务都不会立即关闭,这是并发程序的典型特征。
@@ -584,7 +584,7 @@ synchronized void g() { /* ... */ }
在使用并发时,将字段设为 **private** 特别重要;否则,**synchronized** 关键字不能阻止其他任务直接访问字段,从而产生资源冲突。
-一个线程可以获取对象的锁多次。如果一个方法调用在同一个对象上的第二个方法,而后者又在同一个对象上调用另一个方法,就会发生这种情况。 JVM 会跟踪对象被锁定的次数。如果对象已解锁,则其计数为 0 。当一个线程首次获得锁时,计数变为 1 。每次同一线程在同一对象上获取另一个锁时,计数就会递增。显然,只有首先获得锁的线程才允许多次获取多个锁。每当线程离开 **synchronized** 方法时,计数递减,直到计数变为 0 ,完全释放锁以给其他线程使用。每个类也有一个锁(作为该类的 **Class** 对象的一部分),因此 **synchronized** 静态方法可以在类范围的基础上彼此锁定,不让同时访问静态数据。
+一个线程可以获取对象的锁多次。如果一个方法调用在同一个对象上的第二个方法,而后者又在同一个对象上调用另一个方法,就会发生这种情况。 JVM 会跟踪对象被锁定的次数。如果对象已解锁,则其计数为 0 。当一个线程首次获得锁时,计数变为 1 。每次同一线程在同一对象上获取另一个锁时,计数就会自增。显然,只有首先获得锁的线程才允许多次获取多个锁。每当线程离开 **synchronized** 方法时,计数递减,直到计数变为 0 ,完全释放锁以给其他线程使用。每个类也有一个锁(作为该类的 **Class** 对象的一部分),因此 **synchronized** 静态方法可以在类范围的基础上彼此锁定,不让同时访问静态数据。
你应该什么时候使用同步呢?可以永远 *Brian* 的同步法则[^2]。
@@ -620,7 +620,7 @@ No odd numbers discovered
*/
```
-在两个递增操作之间插入 `Nap()` 构造器方法,以提高在 **currentEvenValue** 是奇数的状态时上下文切换的可能性。因为互斥锁可以阻止多个任务同时进入临界区,所有这不会产生失败。第一个进入 `next()` 方法的任务将获得锁,任何试图获取锁的后续任务都将被阻塞,直到第一个任务释放锁。此时,调度机制选择另一个等待锁的任务。通过这种方式,任何时刻只能有一个任务通过互斥锁保护的代码。
+在两个自增操作之间插入 `Nap()` 构造器方法,以提高在 **currentEvenValue** 是奇数的状态时上下文切换的可能性。因为互斥锁可以阻止多个任务同时进入临界区,所有这不会产生失败。第一个进入 `next()` 方法的任务将获得锁,任何试图获取锁的后续任务都将被阻塞,直到第一个任务释放锁。此时,调度机制选择另一个等待锁的任务。通过这种方式,任何时刻只能有一个任务通过互斥锁保护的代码。
## volatile 关键字
@@ -631,7 +631,7 @@ No odd numbers discovered
### 字分裂
-当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行[^3],这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。
+当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行 [^3] ,这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。
在缺乏任何其他保护的情况下,用 **volatile** 修饰符定义一个 **long** 或 **double** 变量,可阻止字分裂情况。然而,如果使用 **synchronized** 或 **java.util.concurrent.atomic** 类之一保护这些变量,则 **volatile** 将被取代。此外,**volatile** 不会影响到增量操作并不是原子操作的事实。
@@ -701,7 +701,7 @@ public class ReOrdering implements Runnable {
在 Java 线程的讨论中,经常反复提交但不正确的知识是:“原子操作不需要同步”。 一个 *原子操作* 是不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕。依赖于原子性是很棘手且很危险的,如果你是一个并发编程专家,或者你得到了来自这样的专家的帮助,你才应该使用原子性来代替同步,如果你认为自己足够聪明可以应付这种玩火似的情况,那么请接受下面的测试:
-> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步[^4]。
+> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步 [^4] 。
了解原子性是很有用的,并且知道它与其他高级技术一起用于实现一些更加巧妙的 **java.util.concurrent** 库组件。 但是要坚决抵制自己依赖它的冲动。
@@ -774,7 +774,7 @@ i:I
每条指令都会产生一个 “get” 和 “put”,它们之间还有一些其他指令。因此在获取指令和放置指令之间,另有一个任务可能会修改这个属性,所有,这些操作不是原子性的。
-让我们通过定义一个抽象类来测试原子性的概念,这个抽象类的方法是将一个整数类型进行偶数递增,并且 `run()` 不断地调用这个方法:
+让我们通过定义一个抽象类来测试原子性的概念,这个抽象类的方法是将一个整数类型进行偶数自增,并且 `run()` 不断地调用这个方法:
```java
// lowlevel/IntTestable.java
@@ -876,7 +876,7 @@ public class SerialNumbers {
}
```
-**SerialNumbers** 是你可以想象到最简单的类,如果你具备 C++ 或者其他底层的知识背景,你可能会认为递增是一个原子操作,因为 C++ 的递增操作通常被单个微处理器指令所实现(尽管不是以任何一致,可靠,跨平台的方式)。但是,正如前面所提到的,Java 递增操作不是原子性的,并且操作同时涉及读取和写入,因此即使在这样一个简单的操作中,也存在有线程问题的空间。
+**SerialNumbers** 是你可以想象到最简单的类,如果你具备 C++ 或者其他底层的知识背景,你可能会认为自增是一个原子操作,因为 C++ 的自增操作通常被单个微处理器指令所实现(尽管不是以任何一致,可靠,跨平台的方式)。但是,正如前面所提到的,Java 自增操作不是原子性的,并且操作同时涉及读取和写入,因此即使在这样一个简单的操作中,也存在有线程问题的空间。
我们在这里加入 volatile ,看看它是否有帮助。然而,真正的问题是 `nextSerialNumber()` 方法在不进行线程同步的情况下访问共享的可变变量值。
@@ -1363,12 +1363,137 @@ tryLock(2, TimeUnit.SECONDS): false
## 库组件
+**java.util.concurrent** 库提供大量旨在解决并发问题的类,可以帮助你生成更简单,更鲁棒的并发程序。但请注意,这些工具是比起并行流和 **CompletableFuture** 更底层的机制。
+
+在本节中,我们将看一些使用不同组件的示例,然后讨论一下 *lock-free*(无锁) 库组件是如何工作的。
+
### DelayQueue
+这是一个无界阻塞队列 ( **BlockingQueue** ),用于放置实现了 **Delayed** 接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,因此队首对象的延迟到期的时间最长。如果没有任何延迟到期,那么就不会有队首元素,并且 `poll()` 将返回 **null**(正因为这样,你不能将 **null** 放置到这种队列中)。
+
+下面是一个示例,其中的 **Delayed** 对象自身就是任务,而 **DelayedTaskConsumer** 将最“紧急”的任务(到期时间最长的任务)从队列中取出,然后运行它。注意的是这样 **DelayQueue** 就成为了优先级队列的一种变体。
+
+```java
+// lowlevel/DelayQueueDemo.java
+import java.util.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+import static java.util.concurrent.TimeUnit.*;
+
+class DelayedTask implements Runnable, Delayed {
+ private static int counter = 0;
+ private final int id = counter++;
+ private final int delta;
+ private final long trigger;
+ protected static List sequence =
+ new ArrayList<>();
+ DelayedTask(int delayInMilliseconds) {
+ delta = delayInMilliseconds;
+ trigger = System.nanoTime() +
+ NANOSECONDS.convert(delta, MILLISECONDS);
+ sequence.add(this);
+ }
+ @Override
+ public long getDelay(TimeUnit unit) {
+ return unit.convert(
+ trigger - System.nanoTime(), NANOSECONDS);
+ }
+ @Override
+ public int compareTo(Delayed arg) {
+ DelayedTask that = (DelayedTask)arg;
+ if(trigger < that.trigger) return -1;
+ if(trigger > that.trigger) return 1;
+ return 0;
+ }
+ @Override
+ public void run() {
+ System.out.print(this + " ");
+ }
+ @Override
+ public String toString() {
+ return
+ String.format("[%d] Task %d", delta, id);
+ }
+ public String summary() {
+ return String.format("(%d:%d)", id, delta);
+ }
+ public static class EndTask extends DelayedTask {
+ EndTask(int delay) { super(delay); }
+ @Override
+ public void run() {
+ sequence.forEach(dt ->
+ System.out.println(dt.summary()));
+ }
+ }
+}
+
+public class DelayQueueDemo {
+ public static void
+ main(String[] args) throws Exception {
+ DelayQueue tasks =
+ Stream.concat( // Random delays:
+ new Random(47).ints(20, 0, 4000)
+ .mapToObj(DelayedTask::new),
+ // Add the summarizing task:
+ Stream.of(new DelayedTask.EndTask(4000)))
+ .collect(Collectors
+ .toCollection(DelayQueue::new));
+ while(tasks.size() > 0)
+ tasks.take().run();
+ }
+}
+/* Output:
+[128] Task 12 [429] Task 6 [551] Task 13 [555] Task 2
+[693] Task 3 [809] Task 15 [961] Task 5 [1258] Task 1
+[1258] Task 20 [1520] Task 19 [1861] Task 4 [1998] Task
+17 [2200] Task 8 [2207] Task 10 [2288] Task 11 [2522]
+Task 9 [2589] Task 14 [2861] Task 18 [2868] Task 7
+[3278] Task 16 (0:4000)
+(1:1258)
+(2:555)
+(3:693)
+(4:1861)
+(5:961)
+(6:429)
+(7:2868)
+(8:2200)
+(9:2522)
+(10:2207)
+(11:2288)
+(12:128)
+(13:551)
+(14:2589)
+(15:809)
+(16:3278)
+(17:1998)
+(18:2861)
+(19:1520)
+(20:1258)
+*/
+```
+
+**DelayedTask** 包含一个称为 **sequence** 的 **List<DelayedTask>** ,它保存了任务被创建的顺序,因此我们可以看到排序是按照实际发生的顺序执行的。
+
+**Delay** 接口有一个方法, `getDelay()` , 该方法用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期了。这个方法强制我们去使用 **TimeUnit** 类,因为这就是参数类型。这会产生一个非常方便的类,因为你可以很容易地转换单位而无需作任何声明。例如,**delta** 的值是以毫秒为单位存储的,但是 `System.nanoTime()` 产生的时间则是以纳秒为单位的。你可以转换 **delta** 的值,方法是声明它的单位以及你希望以什么单位来表示,就像下面这样:
+
+```java
+NANOSECONDS.convert(delta, MILLISECONDS);
+```
+
+在 `getDelay()` 中, 所希望的单位是作为 **unit** 参数传递进来的,你使用它将当前时间与触发时间之间的差转换为调用者要求的单位,而无需知道这些单位是什么(这是*策略*设计模式的一个简单示例,在这种模式中,算法的一部分是作为参数传递进来的)。
+
+为了排序, **Delayed** 接口还继承了 **Comparable** 接口,因此必须实现 `compareTo()` , 使其可以产生合理的比较。
+
+从输出中可以看到,任务创建的顺序对执行顺序没有任何影响 - 相反,任务是按照所期望的延迟顺序所执行的。
+
### PriorityBlockingQueue
### Lock-Free Collections
+#### The Copying Strategy
+
+#### Compare-And-Swap (CAS)
+
## 本章小结
From 1863ae189f4d5db03e2e2d62cfe316f21d98459c Mon Sep 17 00:00:00 2001
From: AlfredAlan <854723360@mail.com>
Date: Sun, 4 Aug 2019 19:06:09 +0800
Subject: [PATCH 022/475] =?UTF-8?q?=E9=99=84=E5=BD=95=20=E5=B9=B6=E5=8F=91?=
=?UTF-8?q?=E5=BA=95=E5=B1=82=E5=8E=9F=E7=90=86=20=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E8=87=B3=20PriorityBlockingQueue?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Low-Level-Concurrency.md | 157 +++++++++++++++++++-
1 file changed, 153 insertions(+), 4 deletions(-)
diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md
index e7f701dc..21e23571 100644
--- a/docs/book/Appendix-Low-Level-Concurrency.md
+++ b/docs/book/Appendix-Low-Level-Concurrency.md
@@ -631,7 +631,7 @@ No odd numbers discovered
### 字分裂
-当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行 [^3] ,这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。
+当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行[^3],这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。
在缺乏任何其他保护的情况下,用 **volatile** 修饰符定义一个 **long** 或 **double** 变量,可阻止字分裂情况。然而,如果使用 **synchronized** 或 **java.util.concurrent.atomic** 类之一保护这些变量,则 **volatile** 将被取代。此外,**volatile** 不会影响到增量操作并不是原子操作的事实。
@@ -701,7 +701,7 @@ public class ReOrdering implements Runnable {
在 Java 线程的讨论中,经常反复提交但不正确的知识是:“原子操作不需要同步”。 一个 *原子操作* 是不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕。依赖于原子性是很棘手且很危险的,如果你是一个并发编程专家,或者你得到了来自这样的专家的帮助,你才应该使用原子性来代替同步,如果你认为自己足够聪明可以应付这种玩火似的情况,那么请接受下面的测试:
-> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步 [^4] 。
+> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步[^4] 。
了解原子性是很有用的,并且知道它与其他高级技术一起用于实现一些更加巧妙的 **java.util.concurrent** 库组件。 但是要坚决抵制自己依赖它的冲动。
@@ -719,7 +719,7 @@ i++; // Might be atomic in C++
i += 2; // Might be atomic in C++
```
-但是在 C++ 中,这取决于编译器和处理器。你无法编写出依赖于原子性的 C++ 跨平台代码,因为 C++ [^5] 没有像 Java 那样的一致 *内存模型* (memory model)。
+但是在 C++ 中,这取决于编译器和处理器。你无法编写出依赖于原子性的 C++ 跨平台代码,因为 C++ [^5]没有像 Java 那样的一致 *内存模型* (memory model)。
在 Java 中,上面的操作肯定不是原子性的,正如下面的方法产生的 JVM 指令中可以看到的那样:
@@ -1488,6 +1488,155 @@ NANOSECONDS.convert(delta, MILLISECONDS);
### PriorityBlockingQueue
+这是一个很基础的优先级队列,它具有可阻塞的读取操作。在下面的示例中, **Prioritized** 对象会被赋予优先级编号。几个 **Producer** 任务的实例会插入 **Prioritized** 对象到 **PriorityBlockingQueue** 中,但插入之间会有随机延时。然后,单个 **Consumer** 任务在执行 `take()` 时会显示多个选项,**PriorityBlockingQueue** 会将当前具有最高优先级的 **Prioritized** 对象提供给它。
+
+在 **Prioritized** 中的静态变量 **counter** 是 **AtomicInteger** 类型。这是必要的,因为有多个 **Producer** 并行运行;如果不是 **AtomicInteger** 类型,你将会看到重复的 **id** 号。 这个问题在 [并发编程](./24-Concurrent-Programming.md) 的 [构造函数非线程安全](./24-Concurrent-Programming.md) 一节中讨论过。
+
+```java
+// lowlevel/PriorityBlockingQueueDemo.java
+import java.util.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+import onjava.Nap;
+
+class Prioritized implements Comparable {
+ private static AtomicInteger counter =
+ new AtomicInteger();
+ private final int id = counter.getAndIncrement();
+ private final int priority;
+ private static List sequence =
+ new CopyOnWriteArrayList<>();
+ Prioritized(int priority) {
+ this.priority = priority;
+ sequence.add(this);
+ }
+ @Override
+ public int compareTo(Prioritized arg) {
+ return priority < arg.priority ? 1 :
+ (priority > arg.priority ? -1 : 0);
+ }
+ @Override
+ public String toString() {
+ return String.format(
+ "[%d] Prioritized %d", priority, id);
+ }
+ public void displaySequence() {
+ int count = 0;
+ for(Prioritized pt : sequence) {
+ System.out.printf("(%d:%d)", pt.id, pt.priority);
+ if(++count % 5 == 0)
+ System.out.println();
+ }
+ }
+ public static class EndSentinel extends Prioritized {
+ EndSentinel() { super(-1); }
+ }
+}
+
+class Producer implements Runnable {
+ private static AtomicInteger seed =
+ new AtomicInteger(47);
+ private SplittableRandom rand =
+ new SplittableRandom(seed.getAndAdd(10));
+ private Queue queue;
+ Producer(Queue q) {
+ queue = q;
+ }
+ @Override
+ public void run() {
+ rand.ints(10, 0, 20)
+ .mapToObj(Prioritized::new)
+ .peek(p -> new Nap(rand.nextDouble() / 10))
+ .forEach(p -> queue.add(p));
+ queue.add(new Prioritized.EndSentinel());
+ }
+}
+
+class Consumer implements Runnable {
+ private PriorityBlockingQueue q;
+ private SplittableRandom rand =
+ new SplittableRandom(47);
+ Consumer(PriorityBlockingQueue q) {
+ this.q = q;
+ }
+ @Override
+ public void run() {
+ while(true) {
+ try {
+ Prioritized pt = q.take();
+ System.out.println(pt);
+ if(pt instanceof Prioritized.EndSentinel) {
+ pt.displaySequence();
+ break;
+ }
+ new Nap(rand.nextDouble() / 10);
+ } catch(InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
+
+public class PriorityBlockingQueueDemo {
+ public static void main(String[] args) {
+ PriorityBlockingQueue queue =
+ new PriorityBlockingQueue<>();
+ CompletableFuture.runAsync(new Producer(queue));
+ CompletableFuture.runAsync(new Producer(queue));
+ CompletableFuture.runAsync(new Producer(queue));
+ CompletableFuture.runAsync(new Consumer(queue))
+ .join();
+ }
+}
+/* Output:
+[15] Prioritized 2
+[17] Prioritized 1
+[17] Prioritized 5
+[16] Prioritized 6
+[14] Prioritized 9
+[12] Prioritized 0
+[11] Prioritized 4
+[11] Prioritized 12
+[13] Prioritized 13
+[12] Prioritized 16
+[14] Prioritized 18
+[15] Prioritized 23
+[18] Prioritized 26
+[16] Prioritized 29
+[12] Prioritized 17
+[11] Prioritized 30
+[11] Prioritized 24
+[10] Prioritized 15
+[10] Prioritized 22
+[8] Prioritized 25
+[8] Prioritized 11
+[8] Prioritized 10
+[6] Prioritized 31
+[3] Prioritized 7
+[2] Prioritized 20
+[1] Prioritized 3
+[0] Prioritized 19
+[0] Prioritized 8
+[0] Prioritized 14
+[0] Prioritized 21
+[-1] Prioritized 28
+(0:12)(2:15)(1:17)(3:1)(4:11)
+(5:17)(6:16)(7:3)(8:0)(9:14)
+(10:8)(11:8)(12:11)(13:13)(14:0)
+(15:10)(16:12)(17:12)(18:14)(19:0)
+(20:2)(21:0)(22:10)(23:15)(24:11)
+(25:8)(26:18)(27:-1)(28:-1)(29:16)
+(30:11)(31:6)(32:-1)
+*/
+```
+
+与前面的示例一样,**Prioritized** 对象的创建顺序在 **sequence** 的 **list** 对象上所记入,以便与实际执行顺序进行比较。 **EndSentinel** 是用于告知 **Consumer** 对象关闭的特殊类型。
+
+**Producer** 使用 **AtomicInteger** 变量为 **SplittableRandom** 设置随机生成种子,以便不同的 **Producer** 生成不同的队列。 这是必需的,因为多个生产者并行创建,如果不是这样,创建过程并不会是线程安全的。
+
+**Producer** 和 **Consumer** 通过 **PriorityBlockingQueue** 相互连接。因为阻塞队列的性质提供了所有必要的同步,因为阻塞队列的性质提供了所有必要的同步,请注意,显式同步是并不需要的 — 从队列中读取数据时,你不用考虑队列中是否有任何元素,因为队列在没有元素时将阻塞读取。
+
### Lock-Free Collections
#### The Copying Strategy
@@ -1517,7 +1666,7 @@ NANOSECONDS.convert(delta, MILLISECONDS);
[^4]: 这个测试的推论是,“如果某人表示线程是容易并且简单的,请确保这个人没有对你的项目做出重要的决策。如果那个人已经做出,那么你就已经陷入麻烦之中了。”
-[^5]: 这在即将产生的 C++ 的标准中得到了补救
+[^5]: 这在即将产生的 C++ 的标准中得到了补救。
From 293ebe34ee4f6e31b9f8fa862fdaaeea305a7623 Mon Sep 17 00:00:00 2001
From: AlfredAlan <854723360@mail.com>
Date: Mon, 5 Aug 2019 00:26:56 +0800
Subject: [PATCH 023/475] =?UTF-8?q?=E9=99=84=E5=BD=95=20=E5=B9=B6=E5=8F=91?=
=?UTF-8?q?=E5=BA=95=E5=B1=82=E5=8E=9F=E7=90=86=20=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E8=87=B3=20=E6=97=A0=E9=94=81=E9=9B=86=E5=90=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Low-Level-Concurrency.md | 24 ++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md
index 21e23571..d85e110d 100644
--- a/docs/book/Appendix-Low-Level-Concurrency.md
+++ b/docs/book/Appendix-Low-Level-Concurrency.md
@@ -1637,11 +1637,29 @@ public class PriorityBlockingQueueDemo {
**Producer** 和 **Consumer** 通过 **PriorityBlockingQueue** 相互连接。因为阻塞队列的性质提供了所有必要的同步,因为阻塞队列的性质提供了所有必要的同步,请注意,显式同步是并不需要的 — 从队列中读取数据时,你不用考虑队列中是否有任何元素,因为队列在没有元素时将阻塞读取。
-### Lock-Free Collections
+### 无锁集合
-#### The Copying Strategy
+[集合](./12-Collections.md) 章节强调集合是基本的编程工具,这也要求包含并发性。因此,早期的集合比如 **Vector** 和 **Hashtable** 有许多使用 **synchronized** 机制的方法。当这些集合不是在多线程应用中使用时,这就导致了不可接收的开销。在 Java 1.2 版本中,新的集合库是非同步的,而给 **Collection** 类赋予了各种 **static** **synchronized** 修饰的方法来同步不同的集合类型。虽然这是一个改进,因为它让你可以选择是否对集合使用同步,但是开销仍然基于同步锁定。 Java 5 版本添加新的集合类型,专门用于增加线程安全性能,使用巧妙的技术来消除锁定。
-#### Compare-And-Swap (CAS)
+无锁集合有一个有趣的特性:只要读取者仅能看到已完成修改的结果,对集合的修改就可以同时发生在读取发生时。这是通过一些策略实现的。为了让你了解它们是如何工作的,我们来看看其中的一些。
+
+#### 复制策略
+
+使用“复制”策略,修改是在数据结构一部分的单独副本(或有时是整个数据的副本)上进行的,并且在整个修改过程期间这个副本是不可见的。仅当修改完成时,修改后的结构才与“主”数据结构安全地交换,然后读取者才会看到修改。
+
+在 **CopyOnWriteArrayList** ,写入操作会复制整个底层数组。保留原来的数组,以便在修改复制的数组时可以线程安全地进行读取。当修改完成后,原子操作会将其交换到新数组中,以便新的读取操作能够看到新数组内容。 **CopyOnWriteArrayList** 的其中一个好处是,当多个迭代器遍历和修改列表时,它不会抛出 **ConcurrentModificationException** 异常,因此你不用就像过去必须做的那样,编写特殊的代码来防止此类异常。
+
+**CopyOnWriteArraySet** 使用 **CopyOnWriteArrayList** 来实现其无锁行为。
+
+**ConcurrentHashMap** 和 **ConcurrentLinkedQueue** 使用类似的技术来允许并发读写,但是只复制和修改集合的一部分,而不是整个集合。然而,读取者仍然不会看到任何不完整的修改。**ConcurrentHashMap** **不会抛出concurrentmodificationexception** 异常。
+
+#### 比较并交换 (CAS)
+
+在 比较并交换 (CAS) 中,你从内存中获取一个值,并在计算新值时保留原始值。然后使用 CAS 指令,它将原始值与当前内存中的值进行比较,如果这两个值是相等的,则将内存中的旧值替换为计算新值的结果,所有操作都在一个原子操作中完成。如果原始值比较失败,则不会进行交换,因为这意味着另一个线程同时修改了内存。在这种情况下,你的代码必须再次尝试,获取一个新的原始值并重复该操作。
+
+如果内存仅轻量竞争,CAS操作几乎总是在没有重复尝试的情况下完成,因此它非常快。相反,**synchronized** 操作需要考虑每次获取和释放锁的成本,这要昂贵得多,而且没有额外的好处。随着内存竞争的增加,使用 CAS 的操作会变慢,因为它必须更频繁地重复自己的操作,但这是对更多资源竞争的动态响应。这确实是一种优雅的方法。
+
+最重要的是,许多现代处理器的汇编语言中都有一条 CAS 指令,并且也被 JVM 中的 CAS 操作(例如 **Atomic** 类中的操作)所使用。CAS 指令在硬件层面中是原子性的,并且与你所期望的操作一样快。
## 本章小结
From 5c83895b645ed08dc39d08b495e43c7de5cd1f60 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Mon, 5 Aug 2019 02:07:05 +0800
Subject: [PATCH 024/475] fix typos
---
docs/book/06-Housekeeping.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md
index 4a3d2636..b589ee4f 100644
--- a/docs/book/06-Housekeeping.md
+++ b/docs/book/06-Housekeeping.md
@@ -712,7 +712,7 @@ Error: checked out
如前文所述,这里讨论的 Java 虚拟机中,内存分配以较大的"块"为单位。如果对象较大,它会占用单独的块。严格来说,"停止-复制"要求在释放旧对象之前,必须先将所有存活对象从旧堆复制到新堆,这导致了大量的内存复制行为。有了块,垃圾回收器就可以把对象复制到废弃的块。每个块都有年代数来记录自己是否存活。通常,如果块在某处被引用,其年代数加 1,垃圾回收器会对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会复制(只是年代数会增加),含有小型对象的那些块则被复制并整理。Java 虚拟机会监视,如果所有对象都很稳定,垃圾回收的效率降低的话,就切换到"标记-清扫"方式。同样,Java 虚拟机会跟踪"标记-清扫"的效果,如果堆空间出现很多碎片,就会切换回"停止-复制"方式。这就是"自适应"的由来,你可以给它个啰嗦的称呼:"自适应的、分代的、停止-复制、标记-清扫"式的垃圾回收器。
-Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为"即时"(Just-In-Time, JIT)编译器的技术。这种技术可以把程全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快。当需要装载某个类(通常是创建该类的第一个对象)时,编译器会先找到其 **.class** 文件,然后将该类的字节码装入内存。你可以让即时编译器编译所有代码,但这种做法有两个缺点:一是这种加载动作贯穿整个程序生命周期内,累加起来需要花更多时间;二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序速度。另一种做法称为*惰性评估*,意味着即时编译器只有在必要的时候才编译代码。这样,从未被执行的代码也许就压根不会被 JIT 编译。新版 JDK 中的 Java HotSpot 技术就采用了类似的做法,代码每被执行一次就优化一些,所以执行的次数越多,它的速度就越快。
+Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为"即时"(Just-In-Time, JIT)编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快。当需要装载某个类(通常是创建该类的第一个对象)时,编译器会先找到其 **.class** 文件,然后将该类的字节码装入内存。你可以让即时编译器编译所有代码,但这种做法有两个缺点:一是这种加载动作贯穿整个程序生命周期内,累加起来需要花更多时间;二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序速度。另一种做法称为*惰性评估*,意味着即时编译器只有在必要的时候才编译代码。这样,从未被执行的代码也许就压根不会被 JIT 编译。新版 JDK 中的 Java HotSpot 技术就采用了类似的做法,代码每被执行一次就优化一些,所以执行的次数越多,它的速度就越快。
@@ -1054,7 +1054,7 @@ f3(1)
概括一下创建对象的过程,假设有个名为 **Dog** 的类:
-1. 即使没有显式地使用 **static** 关键字,构造器实际上也是静态方法。所以,当首次创建 **Dog** 类型的对象时,或是首次访问 **Dog** 类的静态方法或属性,Java 解释器必须在类路径中查找,以定位 **Dog.class**。
+1. 即使没有显式地使用 **static** 关键字,构造器实际上也是静态方法。所以,当首次创建 **Dog** 类型的对象或是首次访问 **Dog** 类的静态方法或属性时,Java 解释器必须在类路径中查找,以定位 **Dog.class**。
2. 当加载完 **Dog.class** 后(后面会学到,这将创建一个 **Class** 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 **Class** 对象时初始化一次。
3. 当用 `new Dog()` 创建对象时,首先会在堆上为 **Dog** 对象分配足够的存储空间。
4. 分配的存储空间首先会被清零,即会将 **Dog** 对象中的所有基本类型数据设置为默认值(数字会被置为 0,布尔型和字符型也相同),引用被置为 **null**。
@@ -1812,4 +1812,4 @@ This burrito is maybe too hot.
-
\ No newline at end of file
+
From 8f7a8b9b4bb5c134490cd37d814f6471d2041878 Mon Sep 17 00:00:00 2001
From: 1326670425 <1326670425@qq.com>
Date: Mon, 5 Aug 2019 18:41:39 +0800
Subject: [PATCH 025/475] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=AC=AC=E5=8D=81?=
=?UTF-8?q?=E4=BA=8C=E7=AB=A0=20=E9=9B=86=E5=90=88=E7=9A=84=E6=A1=86?=
=?UTF-8?q?=E6=9E=B6=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/12-Collections.md | 6 +++++-
docs/images/collection.png | Bin 0 -> 58326 bytes
docs/images/map.png | Bin 0 -> 20386 bytes
3 files changed, 5 insertions(+), 1 deletion(-)
create mode 100644 docs/images/collection.png
create mode 100644 docs/images/map.png
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 44872d57..199300a8 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -1707,7 +1707,7 @@ Java 提供了许多保存对象的方法:
8. 不要在新代码中使用遗留类 **Vector** ,**Hashtable** 和 **Stack** 。
-浏览一下Java集合的简图(不包含抽象类或遗留组件)会很有帮助。这里仅包括在一般情况下会碰到的接口和类。
+浏览一下Java集合的简图(不包含抽象类或遗留组件)会很有帮助。这里仅包括在一般情况下会碰到的接口和类。(译者注:下图为原著PDF中的截图,可能由于未知原因存在问题。这里可参考译者绘制版[^8])

@@ -1812,6 +1812,10 @@ Serializable]
[^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。
+[^8]: 下面是译者绘制的 Java 集合框架简图,黄色为接口,绿色为抽象类,蓝色为具体类。虚线箭头表示实现关系,实线箭头表示继承关系。
+
+
+
diff --git a/docs/images/collection.png b/docs/images/collection.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab24df4466e7b844ae90f45cfba61a346e564802
GIT binary patch
literal 58326
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