From 1a466bd0348f756303c7142c3dfb3eb495613fd0 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Wed, 7 Aug 2019 13:28:28 +0800
Subject: [PATCH 001/449] [hotfix] fix typos
---
docs/book/08-Reuse.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md
index 04af7f90..4f9526c1 100644
--- a/docs/book/08-Reuse.md
+++ b/docs/book/08-Reuse.md
@@ -210,13 +210,13 @@ Cleanser dilute() apply() scrub()
这演示了一些特性。首先,在 **Cleanser** 的 `append()` 方法中,使用 `+=` 操作符将字符串连接到 **s**,这是 Java 设计人员“重载”来处理字符串的操作符之一 (还有 + )。
-第二,**Cleanser** 和 **Detergent** 都包含一个 `main()` 方法。你可以为每个类创建一个 `main()` ; 这允许对每个类进行简单的测试。当你完成测试时,不需要删除 `main()`; 你可以将其留在以后的测试中。即使程序中有很多类都有 `main()` 方法,惟一运行的只有在命令行上调用的 `main()`。这里,当你使用 **java Detergent** 时候,就调用了 `Detergent.main()`。但是你也可以使用 **java Cleanser** 来调用 `Cleanser.main()`,即使 **Cleanser** 不是一个公共类。即使类具只有包访问权,也可以访问 `public main()`。
+第二,**Cleanser** 和 **Detergent** 都包含一个 `main()` 方法。你可以为每个类创建一个 `main()` ; 这允许对每个类进行简单的测试。当你完成测试时,不需要删除 `main()`; 你可以将其留在以后的测试中。即使程序中有很多类都有 `main()` 方法,惟一运行的只有在命令行上调用的 `main()`。这里,当你使用 **java Detergent** 时候,就调用了 `Detergent.main()`。但是你也可以使用 **java Cleanser** 来调用 `Cleanser.main()`,即使 **Cleanser** 不是一个公共类。即使类只具有包访问权,也可以访问 `public main()`。
在这里,`Detergent.main()` 显式地调用 `Cleanser.main()`,从命令行传递相同的参数(当然,你可以传递任何字符串数组)。
**Cleanser** 中的所有方法都是公开的。请记住,如果不使用任何访问修饰符,则成员默认为包访问权限,这只允许包内成员访问。因此,如果没有访问修饰符,那么包内的任何人都可以使用这些方法。例如,**Detergent** 就没有问题。但是,如果其他包中的类继承 **Cleanser**,则该类只能访问 **Cleanser** 的公共成员。因此,为了允许继承,一般规则是所有字段为私有,所有方法为公共。(受保护成员也允许派生类访问;你以后会知道的。)在特定的情况下,你必须进行调整,但这是一个有用的指南。
-**Cleanser** 的接口中有一组方法: `append()`、`dilute()`、`apply()`、`scrub()` 和 `toString()`。因为 **Detergent** 是从 **Cleanser** 派生的(通过 **extends** 关键字),所以它会在其接口中自动获取所有这些方法,即使你没有在 **Detergent** 中看到所有这些方法的显式定义。你那么,可以把继承看作是复用类。如在 `scrub()` 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 `scrub()` 内部,不能简单地调用 `scrub()`,因为这会产生递归调用。为了解决这个问题,Java的 **super** 关键字引用了当前类继承的“超类”(基类)。因此表达式 `super.scrub()` 调用方法 `scrub()` 的基类版本。
+**Cleanser** 的接口中有一组方法: `append()`、`dilute()`、`apply()`、`scrub()` 和 `toString()`。因为 **Detergent** 是从 **Cleanser** 派生的(通过 **extends** 关键字),所以它会在其接口中自动获取所有这些方法,即使你没有在 **Detergent** 中看到所有这些方法的显式定义。那么,可以把继承看作是复用类。如在 `scrub()` 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 `scrub()` 内部,不能简单地调用 `scrub()`,因为这会产生递归调用。为了解决这个问题,Java的 **super** 关键字引用了当前类继承的“超类”(基类)。因此表达式 `super.scrub()` 调用方法 `scrub()` 的基类版本。
继承时,你不受限于使用基类的方法。你还可以像向类添加任何方法一样向派生类添加新方法:只需定义它。方法 `foam()` 就是一个例子。`Detergent.main()` 中可以看到,对于 **Detergent** 对象,你可以调用 **Cleanser** 和 **Detergent** 中可用的所有方法 (如 `foam()` )。
@@ -507,7 +507,7 @@ PlaceSetting constructor
### 保证适当的清理
-Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你不能知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,你必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在"异常"章节中描述的——你必须通过在 **finally **子句中放置此类清理来防止异常。
+Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在"异常"章节中描述的——你必须通过在 **finally **子句中放置此类清理来防止异常。
请考虑一个在屏幕上绘制图片的计算机辅助设计系统的例子:
From 6c512ae672f66a337f2dc55e71c128464ded5739 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Thu, 8 Aug 2019 09:44:25 +0800
Subject: [PATCH 002/449] [hotfix] fix typos
---
docs/book/08-Reuse.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md
index 4f9526c1..99e6768a 100644
--- a/docs/book/08-Reuse.md
+++ b/docs/book/08-Reuse.md
@@ -898,7 +898,7 @@ public class Wind extends Instrument {
一个被 **static** 和 **final** 同时修饰的属性只会占用一段不能改变的存储空间。
-当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供使任何对象恒定不变的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。
+当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。
下面例子展示了 **final** 属性的使用:
@@ -1062,7 +1062,7 @@ public class FinalArguments {
过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。
-在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理效率问题,只有在防止方法ß覆写时才使用 **final**。
+在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 **final**。
### final 和 private
From 48e4959c7fcede19ba44ed0a4cbc97c9d68ff07f Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Thu, 8 Aug 2019 09:55:37 +0800
Subject: [PATCH 003/449] [hotfix] fix typos
---
docs/book/09-Polymorphism.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md
index aa1b55cb..de16a6ae 100644
--- a/docs/book/09-Polymorphism.md
+++ b/docs/book/09-Polymorphism.md
@@ -153,7 +153,7 @@ Brass.play() MIDDLE_C
## 转机
-运行程序后会看到 **Music.java** 的难点。**Wind.play()** 的输出结果正是我们期望的,但它看起来似乎不能产生我们所期望的结果。观察 `tune()` 方法:
+运行程序后会看到 **Music.java** 的难点。**Wind.play()** 的输出结果正是我们期望的,然而它看起来似乎不应该得出这样的结果。观察 `tune()` 方法:
```java
public static void tune(Instrument i) {
@@ -194,7 +194,7 @@ Shape s = new Circle();
这会创建一个 **Circle** 对象,引用被赋值给 **Shape** 类型的变量 s,这看似错误(将一种类型赋值给另一种类型),然而是没问题的,因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。
-假设你调用了一个基类方法(在派生类中都被覆写):
+假设你调用了一个基类方法(在各个派生类中都被覆写):
```java
s.draw()
@@ -202,7 +202,7 @@ s.draw()
你可能再次认为 **Shape** 的 `draw()` 方法被调用,因为 s 是一个 **Shape** 引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是 **Circle** 的 `draw()` 方法,这是正确的。
-下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 中建立了所有继承自 **Shape** 的类的共同接口——所有的形状都可以被绘画和擦除:
+下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 为从它继承的所有子类建立了公共接口——所有的形状都可以被绘画和擦除:
```java
// polymorphism/shape/Shape.java
@@ -1252,4 +1252,4 @@ RTTI 不仅仅包括简单的转型。例如,它还提供了一种方法,使
-
\ No newline at end of file
+
From 756740d2710e4777a53db1ad544f7a6f9ecd3441 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Thu, 8 Aug 2019 12:13:51 +0800
Subject: [PATCH 004/449] =?UTF-8?q?[hotfix]=20Change=20"=E4=B8=BA=E4=BB=8E?=
=?UTF-8?q?=E5=AE=83=E7=BB=A7=E6=89=BF=E7=9A=84"=20into=20=E2=80=9D?=
=?UTF-8?q?=E4=B8=BA=E5=AE=83=E7=9A=84=E2=80=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/09-Polymorphism.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md
index de16a6ae..3d614a5b 100644
--- a/docs/book/09-Polymorphism.md
+++ b/docs/book/09-Polymorphism.md
@@ -202,7 +202,7 @@ s.draw()
你可能再次认为 **Shape** 的 `draw()` 方法被调用,因为 s 是一个 **Shape** 引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是 **Circle** 的 `draw()` 方法,这是正确的。
-下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 为从它继承的所有子类建立了公共接口——所有的形状都可以被绘画和擦除:
+下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 为它的所有子类建立了公共接口——所有的形状都可以被绘画和擦除:
```java
// polymorphism/shape/Shape.java
From 9cec5768d58733a6853e6920e2c5ab05c48b8625 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Fri, 9 Aug 2019 10:46:34 +0800
Subject: [PATCH 005/449] [hotfix] fix typos
---
docs/book/09-Polymorphism.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md
index 3d614a5b..4f569295 100644
--- a/docs/book/09-Polymorphism.md
+++ b/docs/book/09-Polymorphism.md
@@ -329,7 +329,7 @@ Circle.draw()
### 可扩展性
-现在让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 `tune()` 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 `tune()` 的模型,只与基类接口通信。这样的程序时可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。
+现在让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 `tune()` 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 `tune()` 的模型,只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。
考虑一下乐器的例子,如果在基类中添加更多的方法,并加入一些新类,将会发生什么呢:
@@ -1164,7 +1164,7 @@ SadActor

-这被称作纯粹的“is - a"关系,因为类的接口已经确定了它是什么。继承可以确保任何派生类都拥有基类的接口,绝对不会少。如果按图上这么做,派生类的接口就与基类一样多,也不会多。
+这被称作纯粹的“is - a"关系,因为类的接口已经确定了它是什么。继承可以确保任何派生类都拥有基类的接口,绝对不会少。如果按图上这么做,派生类将只拥有基类的接口。
纯粹的替代意味着派生类可以完美地替代基类,当使用它们时,完全不需要知道这些子类的信息。也就是说,基类可以接收任意发送给派生类的消息,因为它们具有完全相同的接口。只需将派生类向上转型,不要关注对象的具体类型。所有一切都可以通过多态处理。
From d4b913e14efb02f258717b461b08196e9ff7726b Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Fri, 9 Aug 2019 11:05:27 +0800
Subject: [PATCH 006/449] [hotfix] Correct a sentence
---
docs/book/10-Interfaces.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md
index 754d18d2..6a9d90e0 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -760,7 +760,7 @@ Woodwind.play() MIDDLE_C
## 抽象类和接口
-尤其是在 Java 8 引入 **default** 方法之后,区分何时抽象类还是接口是最好的选择变得更加令人困惑。下表做了明确的区分:
+尤其是在 Java 8 引入 **default** 方法之后,选择用抽象类还是用接口变得更加令人困惑。下表做了明确的区分:
| 特性 | 接口 | 抽象类 |
| :------------------: | :--------------------------------------------------------: | :--------------------------------------: |
From 8ba63c6ae9d27fd3e2bd7b6b973353f8c4de2254 Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Fri, 9 Aug 2019 22:10:20 +0800
Subject: [PATCH 007/449] =?UTF-8?q?update=20=E6=9E=9A=E4=B8=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/22-Enumerations.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md
index 1ef7d2d3..8aa6b26f 100644
--- a/docs/book/22-Enumerations.md
+++ b/docs/book/22-Enumerations.md
@@ -5,7 +5,7 @@
> 关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能
-
+在第5章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对Java已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用enum可以做很多有趣的事情,同时,我们也会深入其他的Java特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式.
From dcd3e17096a0269b3cc80683defed627f5ee6fe4 Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Fri, 9 Aug 2019 22:12:43 +0800
Subject: [PATCH 008/449] update
---
docs/book/22-Enumerations.md | 155 ++++++++++++++++++++++++++++++++++-
1 file changed, 152 insertions(+), 3 deletions(-)
diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md
index 8aa6b26f..9db2fdb3 100644
--- a/docs/book/22-Enumerations.md
+++ b/docs/book/22-Enumerations.md
@@ -5,16 +5,165 @@
> 关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能
-在第5章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对Java已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用enum可以做很多有趣的事情,同时,我们也会深入其他的Java特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式.
+在[初始化和清理]()这章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对Java已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用enum可以做很多有趣的事情,同时,我们也会深入其他的Java特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式。
-## 基本功能
+## 基本 enum 特性
+
+我们已经在[初始化和清理]()这章章看到,调用enum的values()方法,可以遍历enum实例 .values()方法返回enum实例的数组,而且该数组中的元素严格保持其在enum中声明时的顺序,因此你可以在循环中使用values()返回的数组。
+
+创建enum时,编译器会为你生成一个相关的类,这个类继承自Java.lang.Enum。下面的例子演示了Enum提供的一些功能:
+
+```java
+// enums/EnumClass.java
+// Capabilities of the Enum class
+enum Shrubbery { GROUND, CRAWLING, HANGING }
+public class EnumClass {
+ public static void main(String[] args) {
+ for(Shrubbery s : Shrubbery.values()) {
+ System.out.println(
+ s + " ordinal: " + s.ordinal());
+ System.out.print(
+ s.compareTo(Shrubbery.CRAWLING) + " ");
+ System.out.print(
+ s.equals(Shrubbery.CRAWLING) + " ");
+ System.out.println(s == Shrubbery.CRAWLING);
+ System.out.println(s.getDeclaringClass());
+ System.out.println(s.name());
+ System.out.println("********************");
+ }
+// Produce an enum value from a String name:
+ for(String s :
+ "HANGING CRAWLING GROUND".split(" ")) {
+ Shrubbery shrub =
+ Enum.valueOf(Shrubbery.class, s);
+ System.out.println(shrub);
+ }
+ }
+}
+```
+
+输出:
+
+```
+GROUND ordinal: 0
+-1 false false
+class Shrubbery
+GROUND
+********************
+CRAWLING ordinal: 1
+0 true true
+class Shrubbery
+CRAWLING
+********************
+HANGING ordinal: 2
+1 false false
+class Shrubbery
+HANGING
+********************
+HANGING
+CRAWLING
+GROUND
+```
+
+ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时,它还实现了Serializable接口。
+
+如果在enum实例上调用getDeclaringClass()方法,我们就能知道其所属的enum类。
+
+name()方法返回enum实例声明时的名字,这与使用toString()方法效果相同。valueOf()是在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在给定名字的实例,将会抛出异常。
+
+### 将静态类型导入用于 enum
+
+先看一看 [初始化和清理]() 这章中Burrito.java的另一个版本:
+
+```java
+// enums/SpicinessEnum.java
+package enums;
+public enum SpicinessEnum {
+ NOT, MILD, MEDIUM, HOT, FLAMING
+}
+// enums/Burrito2.java
+// {java enums.Burrito2}
+package enums;
+import static enums.SpicinessEnum.*;
+public class Burrito2 {
+ SpicinessEnum degree;
+ public Burrito2(SpicinessEnum degree) {
+ this.degree = degree;
+ }
+ @Override
+ public String toString() {
+ return "Burrito is "+ degree;
+ }
+ public static void main(String[] args) {
+ System.out.println(new Burrito2(NOT));
+ System.out.println(new Burrito2(MEDIUM));
+ System.out.println(new Burrito2(HOT));
+ }
+}
+```
+
+输出为:
+
+```
+Burrito is NOT
+Burrito is MEDIUM
+Burrito is HOT
+```
+
+使用static import能够将enum实例的标识符带入当前的命名空间,所以无需再用enum类型来修饰enum实例。这是一个好的想法吗?或者还是显式地修饰enum实例更好?这要看代码的复杂程度了。编译器可以确保你使用的是正确的类型,所以唯一需要担心的是,使用静态导入会不会导致你的代码令人难以理解。多数情况下,使用static import还是有好处的,不过,程序员还是应该对具体情况进行具体分析。
+
+注意,在定义enum的同一个文件中,这种技巧无法使用,如果是在默认包中定义enum,这种技巧也无法使用(在Sun内部对这一点显然也有不同意见)。
## 方法添加
+除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类。也就是说我们可以向enum中添加方法。enum甚至可以有main()方法。
+
+一般来说,我们希望每个枚举实例能够返回对自身的描述,而不仅仅只是默认的toString()实现,这只能返回枚举实例的名字。为此,你可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。看一看下面的示例:
+
+```java
+// enums/OzWitch.java
+// The witches in the land of Oz
+public enum OzWitch {
+ // Instances must be defined first, before methods:
+ WEST("Miss Gulch, aka the Wicked Witch of the West"),
+ NORTH("Glinda, the Good Witch of the North"),
+ EAST("Wicked Witch of the East, wearer of the Ruby " +
+ "Slippers, crushed by Dorothy's house"),
+ SOUTH("Good by inference, but missing");
+ private String description;
+ // Constructor must be package or private access:
+ private OzWitch(String description) {
+ this.description = description;
+ }
+ public String getDescription() { return description; }
+ public static void main(String[] args) {
+ for(OzWitch witch : OzWitch.values())
+ System.out.println(
+ witch + ": " + witch.getDescription());
+ }
+}
+```
+
+输出为:
+
+```
+WEST: Miss Gulch, aka the Wicked Witch of the West
+NORTH: Glinda, the Good Witch of the North
+EAST: Wicked Witch of the East, wearer of the Ruby
+Slippers, crushed by Dorothy's house
+SOUTH: Good by inference, but missing
+```
+
+注意,如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java要求你必须先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。
+
+enum中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum就是一个普通的类。所以,我们可以使用enum做许多事情(虽然,我们一般只使用普通的枚举类型)
+
+在这个例子中,虽然我们有意识地将enum的构造器声明为private,但对于它的可访问性而言,其实并没有什么变化,因为(即使不声明为private)我们只能在enum定义的内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。
+
## switch语句
@@ -27,8 +176,8 @@
## 实现而非继承
-
+
## 随机选择
From bab7504523262b6e3146f11e5c85b3796f63a5db Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Fri, 9 Aug 2019 23:00:38 +0800
Subject: [PATCH 009/449] update
---
docs/book/22-Enumerations.md | 389 +++++++++++++++++++++++++++++++++--
1 file changed, 369 insertions(+), 20 deletions(-)
diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md
index 9db2fdb3..cb6a29fc 100644
--- a/docs/book/22-Enumerations.md
+++ b/docs/book/22-Enumerations.md
@@ -3,17 +3,17 @@
# 第二十二章 枚举
-> 关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能
+> 关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能
-在[初始化和清理]()这章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对Java已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用enum可以做很多有趣的事情,同时,我们也会深入其他的Java特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式。
+在[初始化和清理 ]() 这章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对 Java 已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用 enum 可以做很多有趣的事情,同时,我们也会深入其他的 Java 特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式。
## 基本 enum 特性
-我们已经在[初始化和清理]()这章章看到,调用enum的values()方法,可以遍历enum实例 .values()方法返回enum实例的数组,而且该数组中的元素严格保持其在enum中声明时的顺序,因此你可以在循环中使用values()返回的数组。
+我们已经在[初始化和清理 ]() 这章章看到,调用 enum 的 values() 方法,可以遍历 enum 实例 .values() 方法返回 enum 实例的数组,而且该数组中的元素严格保持其在 enum 中声明时的顺序,因此你可以在循环中使用 values() 返回的数组。
-创建enum时,编译器会为你生成一个相关的类,这个类继承自Java.lang.Enum。下面的例子演示了Enum提供的一些功能:
+创建 enum 时,编译器会为你生成一个相关的类,这个类继承自 Java.lang.Enum。下面的例子演示了 Enum 提供的一些功能:
```java
// enums/EnumClass.java
@@ -67,15 +67,15 @@ CRAWLING
GROUND
```
-ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。Enum类实现了Comparable接口,所以它具有compareTo()方法。同时,它还实现了Serializable接口。
+ordinal() 方法返回一个 int 值,这是每个 enum 实例在声明时的次序,从 0 开始。可以使用==来比较 enum 实例,编译器会自动为你提供 equals() 和 hashCode() 方法。Enum 类实现了 Comparable 接口,所以它具有 compareTo() 方法。同时,它还实现了 Serializable 接口。
-如果在enum实例上调用getDeclaringClass()方法,我们就能知道其所属的enum类。
+如果在 enum 实例上调用 getDeclaringClass() 方法,我们就能知道其所属的 enum 类。
-name()方法返回enum实例声明时的名字,这与使用toString()方法效果相同。valueOf()是在Enum中定义的static方法,它根据给定的名字返回相应的enum实例,如果不存在给定名字的实例,将会抛出异常。
+name() 方法返回 enum 实例声明时的名字,这与使用 toString() 方法效果相同。valueOf() 是在 Enum 中定义的 static 方法,它根据给定的名字返回相应的 enum 实例,如果不存在给定名字的实例,将会抛出异常。
### 将静态类型导入用于 enum
-先看一看 [初始化和清理]() 这章中Burrito.java的另一个版本:
+先看一看 [初始化和清理 ]() 这章中 Burrito.java 的另一个版本:
```java
// enums/SpicinessEnum.java
@@ -112,17 +112,17 @@ Burrito is MEDIUM
Burrito is HOT
```
-使用static import能够将enum实例的标识符带入当前的命名空间,所以无需再用enum类型来修饰enum实例。这是一个好的想法吗?或者还是显式地修饰enum实例更好?这要看代码的复杂程度了。编译器可以确保你使用的是正确的类型,所以唯一需要担心的是,使用静态导入会不会导致你的代码令人难以理解。多数情况下,使用static import还是有好处的,不过,程序员还是应该对具体情况进行具体分析。
+使用 static import 能够将 enum 实例的标识符带入当前的命名空间,所以无需再用 enum 类型来修饰 enum 实例。这是一个好的想法吗?或者还是显式地修饰 enum 实例更好?这要看代码的复杂程度了。编译器可以确保你使用的是正确的类型,所以唯一需要担心的是,使用静态导入会不会导致你的代码令人难以理解。多数情况下,使用 static import 还是有好处的,不过,程序员还是应该对具体情况进行具体分析。
-注意,在定义enum的同一个文件中,这种技巧无法使用,如果是在默认包中定义enum,这种技巧也无法使用(在Sun内部对这一点显然也有不同意见)。
+注意,在定义 enum 的同一个文件中,这种技巧无法使用,如果是在默认包中定义 enum,这种技巧也无法使用(在 Sun 内部对这一点显然也有不同意见)。
## 方法添加
-除了不能继承自一个enum之外,我们基本上可以将enum看作一个常规的类。也就是说我们可以向enum中添加方法。enum甚至可以有main()方法。
+除了不能继承自一个 enum 之外,我们基本上可以将 enum 看作一个常规的类。也就是说我们可以向 enum 中添加方法。enum 甚至可以有 main() 方法。
-一般来说,我们希望每个枚举实例能够返回对自身的描述,而不仅仅只是默认的toString()实现,这只能返回枚举实例的名字。为此,你可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。看一看下面的示例:
+一般来说,我们希望每个枚举实例能够返回对自身的描述,而不仅仅只是默认的 toString() 实现,这只能返回枚举实例的名字。为此,你可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。看一看下面的示例:
```java
// enums/OzWitch.java
@@ -158,39 +158,388 @@ Slippers, crushed by Dorothy's house
SOUTH: Good by inference, but missing
```
-注意,如果你打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号。同时,Java要求你必须先定义enum实例。如果在定义enum实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。
+注意,如果你打算定义自己的方法,那么必须在 enum 实例序列的最后添加一个分号。同时,Java 要求你必须先定义 enum 实例。如果在定义 enum 实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。
+
+enum 中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum 就是一个普通的类。所以,我们可以使用 enum 做许多事情(虽然,我们一般只使用普通的枚举类型)
+
+在这个例子中,虽然我们有意识地将 enum 的构造器声明为 private,但对于它的可访问性而言,其实并没有什么变化,因为(即使不声明为 private)我们只能在 enum 定义的内部使用其构造器创建 enum 实例。一旦 enum 的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。
+
+### 覆盖 enum 的方法
+
+覆盖 toSring() 方法,给我们提供了另一种方式来为枚举实例生成不同的字符串描述信息。
+在下面的示例中,我们使用的就是实例的名字,不过我们希望改变其格式。覆盖 enum 的 toSring() 方法与覆盖一般类的方法没有区别:
+
+```java
+// enums/SpaceShip.java
+import java.util.stream.*;
+public enum SpaceShip {
+ SCOUT, CARGO, TRANSPORT,
+ CRUISER, BATTLESHIP, MOTHERSHIP;
+ @Override
+ public String toString() {
+ String id = name();
+ String lower = id.substring(1).toLowerCase();
+ return id.charAt(0) + lower;
+ }
+ public static void main(String[] args) {
+ Stream.of(values())
+ .forEach(System.out::println);
+ }
+}
+```
+
+输出为:
+
+```
+Scout
+Cargo
+Transport
+Cruiser
+Battleship
+Mothership
+```
+
+toString() 方法通过调用 name() 方法取得 SpaceShip 的名字,然后将其修改为只有首字母大写的格式。
-enum中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum就是一个普通的类。所以,我们可以使用enum做许多事情(虽然,我们一般只使用普通的枚举类型)
-在这个例子中,虽然我们有意识地将enum的构造器声明为private,但对于它的可访问性而言,其实并没有什么变化,因为(即使不声明为private)我们只能在enum定义的内部使用其构造器创建enum实例。一旦enum的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。
-## switch语句
+## switch 语句中的 enum
+
+在 switch 中使用 enum,是 enum 提供的一项非常便利的功能。一般来说,在 switch 中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过 ordinal() 方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在 switch 语句中使用 enum。
+
+虽然一般情况下我们必须使用 enum 类型来修饰一个 enum 实例,但是在 case 语句中却不必如此。下面的例子使用 enum 构造了一个小型状态机:
+
+```java
+// enums/TrafficLight.java
+// Enums in switch statements
+// Define an enum type:
+enum Signal { GREEN, YELLOW, RED, }
+
+public class TrafficLight {
+ Signal color = Signal.RED;
+ public void change() {
+ switch(color) {
+ // Note you don't have to say Signal.RED
+ // in the case statement:
+ case RED: color = Signal.GREEN;
+ break;
+ case GREEN: color = Signal.YELLOW;
+ break;
+ case YELLOW: color = Signal.RED;
+ break;
+ }
+ }
+ @Override
+ public String toString() {
+ return "The traffic light is " + color;
+ }
+ public static void main(String[] args) {
+ TrafficLight t = new TrafficLight();
+ for(int i = 0; i < 7; i++) {
+ System.out.println(t);
+ t.change();
+ }
+ }
+}
+```
+
+输出为:
+
+```
+The traffic light is RED
+The traffic light is GREEN
+The traffic light is YELLOW
+The traffic light is RED
+The traffic light is GREEN
+The traffic light is YELLOW
+The traffic light is RED
+```
+
+编译器并没有抱怨 switch 中没有 default 语句,但这并不是因为每一个 Signal 都有对应的 case 语句。如果你注释掉其中的某个 case 语句,编译器同样不会抱怨什么。这意味着,你必须确保自己覆盖了所有的分支。但是,如果在 case 语句中调用 return,那么编译器就会抱怨缺少 default 语句了。这与是否覆盖了 enum 的所有实例无关。
+
+
-## values方法
+## values 方法的神秘之处
+
+前面已经提到,编译器为你创建的 enum 类都继承自 Enum 类。然而,如果你研究一下 Enum 类就会发现,它并没有 values() 方法。可我们明明已经用过该方法了,难道存在某种“隐藏的”方法吗?我们可以利用反射机制编写一个简单的程序,来查看其中的究竟:
+
+```java
+// enums/Reflection.java
+// Analyzing enums using reflection
+import java.lang.reflect.*;
+import java.util.*;
+import onjava.*;
+enum Explore { HERE, THERE }
+public class Reflection {
+ public static
+ Set analyze(Class> enumClass) {
+ System.out.println(
+ "_____ Analyzing " + enumClass + " _____");
+ System.out.println("Interfaces:");
+ for(Type t : enumClass.getGenericInterfaces())
+ System.out.println(t);
+ System.out.println(
+ "Base: " + enumClass.getSuperclass());
+ System.out.println("Methods: ");
+ Set methods = new TreeSet<>();
+ for(Method m : enumClass.getMethods())
+ methods.add(m.getName());
+ System.out.println(methods);
+ return methods;
+ }
+ public static void main(String[] args) {
+ Set exploreMethods =
+ analyze(Explore.class);
+ Set enumMethods = analyze(Enum.class);
+ System.out.println(
+ "Explore.containsAll(Enum)? " +
+ exploreMethods.containsAll(enumMethods));
+ System.out.print("Explore.removeAll(Enum): ");
+ exploreMethods.removeAll(enumMethods);
+ System.out.println(exploreMethods);
+// Decompile the code for the enum:
+ OSExecute.command(
+ "javap -cp build/classes/main Explore");
+ }
+}
+```
+
+输出为:
+
+```java
+_____ Analyzing class Explore _____
+Interfaces:
+Base: class java.lang.Enum
+Methods:
+[compareTo, equals, getClass, getDeclaringClass,
+hashCode, name, notify, notifyAll, ordinal, toString,
+valueOf, values, wait]
+_____ Analyzing class java.lang.Enum _____
+Interfaces:
+java.lang.Comparable
+interface java.io.Serializable
+Base: class java.lang.Object
+Methods:
+[compareTo, equals, getClass, getDeclaringClass,
+hashCode, name, notify, notifyAll, ordinal, toString,
+valueOf, wait]
+Explore.containsAll(Enum)? true
+Explore.removeAll(Enum): [values]
+Compiled from "Reflection.java"
+final class Explore extends java.lang.Enum {
+ public static final Explore HERE;
+ public static final Explore THERE;
+ public static Explore[] values();
+ public static Explore valueOf(java.lang.String);
+ static {};
+}
+```
+
+答案是,values() 是由编译器添加的 static 方法。可以看出,在创建 Explore 的过程中,编译器还为其添加了 valueOf() 方法。这可能有点令人迷惑,Enum 类不是已经有 valueOf() 方法了吗。
+
+不过 Enum 中的 valueOf() 方法需要两个参数,而这个新增的方法只需一个参数。由于这里使用的 Set 只存储方法的名字,而不考虑方法的签名,所以在调用 Explore.removeAll(Enum) 之后,就只剩下[values] 了。
+
+从最后的输出中可以看到,编译器将 Explore 标记为 final 类,所以无法继承自 enum,其中还有一个 static 的初始化子句,稍后我们将学习如何重定义该句。
+
+由于擦除效应(在[泛型 ]() 章节中介绍过),反编译无法得到 Enum 的完整信息,所以它展示的 Explore 的父类只是一个原始的 Enum,而非事实上的 Enum\。
+
+由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants0 方法,所以即便 Enum 接口中没有 values0 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。
+
+```java
+// enums/UpcastEnum.java
+// No values() method if you upcast an enum
+enum Search { HITHER, YON }
+public class UpcastEnum {
+ public static void main(String[] args) {
+ Search[] vals = Search.values();
+ Enum e = Search.HITHER; // Upcast
+// e.values(); // No values() in Enum
+ for(Enum en : e.getClass().getEnumConstants())
+ System.out.println(en);
+ }
+}
+```
+
+输出为:
+
+```
+HITHER
+YON
+```
+
+因为 getEnumConstants() 是 Class 上的方法,所以你甚至可以对不是枚举的类调用此方法:
+
+```java
+// enums/NonEnum.java
+public class NonEnum {
+ public static void main(String[] args) {
+ Class intClass = Integer.class;
+ try {
+ for(Object en : intClass.getEnumConstants())
+ System.out.println(en);
+ } catch(Exception e) {
+ System.out.println("Expected: " + e);
+ }
+ }
+}
+```
+
+输出为:
+
+```java
+Expected: java.lang.NullPointerException
+```
+
+只不过,此时该方法返回 null,所以当你试图使用其返回的结果时会发生异常。
## 实现而非继承
+我们已经知道,所有的 enum 都继承自 Java.lang.Enum 类。由于 Java 不支持多重继承,所以你的 enum 不能再继承其他类:
+
+```java
+enum NotPossible extends Pet { ... // Won't work
+```
+
+然而,在我们创建一个新的 enum 时,可以同时实现一个或多个接口:
+
+```java
+// enums/cartoons/EnumImplementation.java
+// An enum can implement an interface
+// {java enums.cartoons.EnumImplementation}
+package enums.cartoons;
+import java.util.*;
+import java.util.function.*;
+enum CartoonCharacter
+ implements Supplier {
+ SLAPPY, SPANKY, PUNCHY,
+ SILLY, BOUNCY, NUTTY, BOB;
+ private Random rand =
+ new Random(47);
+ @Override
+ public CartoonCharacter get() {
+ return values()[rand.nextInt(values().length)];
+ }
+}
+public class EnumImplementation {
+ public static void printNext(Supplier rg) {
+ System.out.print(rg.get() + ", ");
+ }
+ public static void main(String[] args) {
+// Choose any instance:
+ CartoonCharacter cc = CartoonCharacter.BOB;
+ for(int i = 0; i < 10; i++)
+ printNext(cc);
+ }
+}
+```
+
+输出为:
+
+```
+BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY,
+NUTTY, SLAPPY,
+```
+
+这个结果有点奇怪,不过你必须要有一个 enum 实例才能调用其上的方法。现在,在任何接受 Supplier 参数的方法中,例如 printNext(),都可以使用 CartoonCharacter。
+
## 随机选择
+就像你在 CartoonCharacter.get() 中看到的那样,本章中的很多示例都需要从 enum 实例中进行随机选择。我们可以利用泛型,从而使得这个工作更一般化,并将其加入到我们的工具库中。
+
+```java
+// onjava/Enums.java
+package onjava;
+import java.util.*;
+public class Enums {
+ private static Random rand = new Random(47);
+
+ public static > T random(Class ec) {
+ return random(ec.getEnumConstants());
+ }
+
+ public static T random(T[] values) {
+ return values[rand.nextInt(values.length)];
+ }
+}
+```
+
+古怪的语法\\> 表示 T 是一个 enum 实例。而将 Class\ 作为参数的话,我们就可以利用 Class 对象得到 enum 实例的数组了。重载后的 random() 方法只需使用 T[] 作为参数,因为它并不会调用 Enum 上的任何操作,它只需从数组中随机选择一个元素即可。这样,最终的返回类型正是 enum 的类型。
+
+下面是 random() 方法的一个简单示例:
+
+```java
+// enums/RandomTest.java
+import onjava.*;
+enum Activity { SITTING, LYING, STANDING, HOPPING,
+ RUNNING, DODGING, JUMPING, FALLING, FLYING }
+
+public class RandomTest {
+ public static void main(String[] args) {
+ for(int i = 0; i < 20; i++)
+ System.out.print(
+ Enums.random(Activity.class) + " ");
+ }
+}
+```
+
+输出为:
+
+```
+STANDING FLYING RUNNING STANDING RUNNING STANDING LYING
+DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING
+STANDING LYING FALLING RUNNING FLYING LYING
+```
+
+
+
-## 使用接口组织
+## 使用接口组织枚举
+
+无法从 enum 继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。
+
+在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。举例来说,假设你想用 enum 来表示不同类别的食物,同时还希望每个 enum 元素仍然保持 Food 类型。那可以这样实现:
+
+```java
+// enums/menu/Food.java
+// Subcategorization of enums within interfaces
+package enums.menu;
+public interface Food {
+ enum Appetizer implements Food {
+ SALAD, SOUP, SPRING_ROLLS;
+ }
+ enum MainCourse implements Food {
+ LASAGNE, BURRITO, PAD_THAI,
+ LENTILS, HUMMOUS, VINDALOO;
+ }
+ enum Dessert implements Food {
+ TIRAMISU, GELATO, BLACK_FOREST_CAKE,
+ FRUIT, CREME_CARAMEL;
+ }
+ enum Coffee implements Food {
+ BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
+ LATTE, CAPPUCCINO, TEA, HERB_TEA;
+ }
+}
+```
+
+
-## 使用EnumSet替代Flags
+## 使用 EnumSet 替代 Flags
-## 使用EnumMap
+## 使用 EnumMap
From 7def85e10f32d355a9a8251dbea9e0b5ace4811c Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Sat, 10 Aug 2019 08:33:02 +0800
Subject: [PATCH 010/449] [hotfix] fix a few mistakes
---
docs/book/10-Interfaces.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md
index 6a9d90e0..f61395ff 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -1076,7 +1076,7 @@ Waveform 0

-在一个派生类中,你不能要求必须有一个是抽象的活“具体的”(没有任何抽象方法)的基类。如果确实要继承一个非接口的类,那么只能继承一个,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个组合了多接口的具体类产生了新类:
+派生类并不要求必须继承自抽象的或“具体的”(没有任何抽象方法)的基类。如果继承一个非接口的类,那么只能从一个类继承,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个由多个接口组合而成的具体类产生的新类:
```java
// interfaces/Adventure.java
From d507b1cdf57276402bc62e64711d237daf593fc7 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Sat, 10 Aug 2019 15:19:54 +0800
Subject: [PATCH 011/449] [hotfix] Make sentence more fluent
---
docs/book/10-Interfaces.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md
index f61395ff..f2a4f21b 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -1076,7 +1076,7 @@ Waveform 0

-派生类并不要求必须继承自抽象的或“具体的”(没有任何抽象方法)的基类。如果继承一个非接口的类,那么只能从一个类继承,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个由多个接口组合而成的具体类产生的新类:
+派生类并不要求必须继承自抽象的或“具体的”(没有任何抽象方法)的基类。如果继承一个非接口的类,那么只能继承一个类,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个由多个接口组合而成的具体类产生的新类:
```java
// interfaces/Adventure.java
From f86413690f3a6ab26240b726a9912c17c23fda99 Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Sat, 10 Aug 2019 23:14:59 +0800
Subject: [PATCH 012/449] update
---
docs/book/22-Enumerations.md | 1561 +++++++++++++++++++++++++++++++++-
1 file changed, 1556 insertions(+), 5 deletions(-)
diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md
index cb6a29fc..309d9cfe 100644
--- a/docs/book/22-Enumerations.md
+++ b/docs/book/22-Enumerations.md
@@ -1,6 +1,7 @@
[TOC]
+
# 第二十二章 枚举
> 关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能
@@ -203,8 +204,8 @@ toString() 方法通过调用 name() 方法取得 SpaceShip 的名字,然后
-
+
## switch 语句中的 enum
在 switch 中使用 enum,是 enum 提供的一项非常便利的功能。一般来说,在 switch 中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过 ordinal() 方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在 switch 语句中使用 enum。
@@ -261,8 +262,8 @@ The traffic light is RED
-
+
## values 方法的神秘之处
前面已经提到,编译器为你创建的 enum 类都继承自 Enum 类。然而,如果你研究一下 Enum 类就会发现,它并没有 values() 方法。可我们明明已经用过该方法了,难道存在某种“隐藏的”方法吗?我们可以利用反射机制编写一个简单的程序,来查看其中的究竟:
@@ -396,8 +397,8 @@ Expected: java.lang.NullPointerException
只不过,此时该方法返回 null,所以当你试图使用其返回的结果时会发生异常。
-
+
## 实现而非继承
我们已经知道,所有的 enum 都继承自 Java.lang.Enum 类。由于 Java 不支持多重继承,所以你的 enum 不能再继承其他类:
@@ -500,8 +501,8 @@ STANDING LYING FALLING RUNNING FLYING LYING
-
+
## 使用接口组织枚举
无法从 enum 继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。
@@ -531,28 +532,1578 @@ public interface Food {
}
```
+对于 enum 而言,实现接口是使其子类化的唯一办法,所以嵌入在 Food 中的每个 enum 都实现了 Food 接口。现在,在下面的程序中,我们可以说“所有东西都是某种类型的 Food"。
+
+```java
+// enums/menu/TypeOfFood.java
+// {java enums.menu.TypeOfFood}
+package enums.menu;
+import static enums.menu.Food.*;
+public class TypeOfFood {
+ public static void main(String[] args) {
+ Food food = Appetizer.SALAD;
+ food = MainCourse.LASAGNE;
+ food = Dessert.GELATO;
+ food = Coffee.CAPPUCCINO;
+ }
+}
+```
+
+如果 enum 类型实现了 Food 接口,那么我们就可以将其实例向上转型为 Food,所以上例中的所有东西都是 Food。
+
+然而,当你需要与一大堆类型打交道时,接口就不如 enum 好用了。例如,如果你想创建一个“校举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类:
+
+```java
+// enums/menu/Course.java
+package enums.menu;
+import onjava.*;
+public enum Course {
+ APPETIZER(Food.Appetizer.class),
+ MAINCOURSE(Food.MainCourse.class),
+ DESSERT(Food.Dessert.class),
+ COFFEE(Food.Coffee.class);
+ private Food[] values;
+ private Course(Class extends Food> kind) {
+ values = kind.getEnumConstants();
+ }
+ public Food randomSelection() {
+ return Enums.random(values);
+ }
+}
+```
+
+每一个 Course 的实例都将其对应的 Class 对象作为构造器的参数。通过 getEnumConstants0 方法,可以从该 Class 对象中取得某个 Food 子类的所有 enum 实例。这些实例在 randomSelection() 中被用到。因此,通过从每一个 Course 实例中随机地选择一个 Food,我们便能够生成一份菜单:
+
+```java
+// enums/menu/Meal.java
+// {java enums.menu.Meal}
+package enums.menu;
+public class Meal {
+ public static void main(String[] args) {
+ for(int i = 0; i < 5; i++) {
+ for(Course course : Course.values()) {
+ Food food = course.randomSelection();
+ System.out.println(food);
+ }
+ System.out.println("***");
+ }
+ }
+}
+```
+
+输出为:
+```
+SPRING_ROLLS
+VINDALOO
+FRUIT
+DECAF_COFFEE
+***
+SOUP
+VINDALOO
+FRUIT
+TEA
+***
+SALAD
+BURRITO
+FRUIT
+TEA
+***
+SALAD
+BURRITO
+CREME_CARAMEL
+LATTE
+***
+SOUP
+BURRITO
+TIRAMISU
+ESPRESSO
+***
+```
+
+在这个例子中,我们通过遍历每一个 Course 实例来获得“枚举的枚举”的值。稍后,在 VendingMachine.java 中,我们会看到另一种组织枚举实例的方式,但其也有一些其他的限制。
+
+此外,还有一种更简洁的管理枚举的办法,就是将一个 enum 嵌套在另一个 enum 内。就像这样:
+```java
+// enums/SecurityCategory.java
+// More succinct subcategorization of enums
+import onjava.*;
+enum SecurityCategory {
+ STOCK(Security.Stock.class),
+ BOND(Security.Bond.class);
+ Security[] values;
+ SecurityCategory(Class extends Security> kind) {
+ values = kind.getEnumConstants();
+ }
+ interface Security {
+ enum Stock implements Security {
+ SHORT, LONG, MARGIN
+ }
+ enum Bond implements Security {
+ MUNICIPAL, JUNK
+ }
+ }
+ public Security randomSelection() {
+ return Enums.random(values);
+ }
+ public static void main(String[] args) {
+ for(int i = 0; i < 10; i++) {
+ SecurityCategory category =
+ Enums.random(SecurityCategory.class);
+ System.out.println(category + ": " +
+ category.randomSelection());
+ }
+ }
+}
+```
+
+输出为:
+
+```
+BOND: MUNICIPAL
+BOND: MUNICIPAL
+STOCK: MARGIN
+STOCK: MARGIN
+BOND: JUNK
+STOCK: SHORT
+STOCK: LONG
+STOCK: LONG
+BOND: MUNICIPAL
+BOND: JUNK
+```
+
+Security 接口的作用是将其所包含的 enum 组合成一个公共类型,这一点是有必要的。然后,SecurityCategory 才能将 Security 中的 enum 作为其构造器的参数使用,以起到组织的效果。
+
+如果我们将这种方式应用于 Food 的例子,结果应该这样:
+
+```java
+// enums/menu/Meal2.java
+// {java enums.menu.Meal2}
+package enums.menu;
+import onjava.*;
+public enum Meal2 {
+ APPETIZER(Food.Appetizer.class),
+ MAINCOURSE(Food.MainCourse.class),
+ DESSERT(Food.Dessert.class),
+ COFFEE(Food.Coffee.class);
+ private Food[] values;
+ private Meal2(Class extends Food> kind) {
+ values = kind.getEnumConstants();
+ }
+ public interface Food {
+ enum Appetizer implements Food {
+ SALAD, SOUP, SPRING_ROLLS;
+ }
+ enum MainCourse implements Food {
+ LASAGNE, BURRITO, PAD_THAI,
+ LENTILS, HUMMOUS, VINDALOO;
+ }
+ enum Dessert implements Food {
+ TIRAMISU, GELATO, BLACK_FOREST_CAKE,
+ FRUIT, CREME_CARAMEL;
+ }
+ enum Coffee implements Food {
+ BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
+ LATTE, CAPPUCCINO, TEA, HERB_TEA;
+ }
+ }
+ public Food randomSelection() {
+ return Enums.random(values);
+ }
+ public static void main(String[] args) {
+ for(int i = 0; i < 5; i++) {
+ for(Meal2 meal : Meal2.values()) {
+ Food food = meal.randomSelection();
+ System.out.println(food);
+ }
+ System.out.println("***");
+ }
+ }
+}
+```
+
+输出为:
+
+```
+SPRING_ROLLS
+VINDALOO
+FRUIT
+DECAF_COFFEE
+***
+SOUP
+VINDALOO
+FRUIT
+TEA
+***
+SALAD
+BURRITO
+FRUIT
+TEA
+***
+SALAD
+BURRITO
+CREME_CARAMEL
+LATTE
+***
+SOUP
+BURRITO
+TIRAMISU
+ESPRESSO
+***
+```
+
+其实,这仅仅是重新组织了一下代码,不过多数情况下,这种方式使你的代码具有更清晰的结构。
+
## 使用 EnumSet 替代 Flags
+Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enumi 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。
+
+EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。
+
+EnumSet 中的元素必须来自一个 enum。下面的 enum 表示在一座大楼中,警报传感器的安放位置:
+
+```java
+// enums/AlarmPoints.java
+package enums;
+public enum AlarmPoints {
+ STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,
+ OFFICE4, BATHROOM, UTILITY, KITCHEN
+}
+```
+
+然后,我们用 EnumSet 来跟踪报警器的状态:
+
+```java
+// enums/EnumSets.java
+// Operations on EnumSets
+// {java enums.EnumSets}
+package enums;
+import java.util.*;
+import static enums.AlarmPoints.*;
+public class EnumSets {
+ public static void main(String[] args) {
+ EnumSet points =
+ EnumSet.noneOf(AlarmPoints.class); // Empty
+ points.add(BATHROOM);
+ System.out.println(points);
+ points.addAll(
+ EnumSet.of(STAIR1, STAIR2, KITCHEN));
+ System.out.println(points);
+ points = EnumSet.allOf(AlarmPoints.class);
+ points.removeAll(
+ EnumSet.of(STAIR1, STAIR2, KITCHEN));
+ System.out.println(points);
+ points.removeAll(
+ EnumSet.range(OFFICE1, OFFICE4));
+ System.out.println(points);
+ points = EnumSet.complementOf(points);
+ System.out.println(points);
+ }
+}
+```
+
+输出为:
+
+```java
+[BATHROOM]
+[STAIR1, STAIR2, BATHROOM, KITCHEN]
+[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM,
+UTILITY]
+[LOBBY, BATHROOM, UTILITY]
+[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4,
+KITCHEN]
+```
+
+使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnunSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。
+
+EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在。
+也就是说,在不超过一个 long 的表达能力的情况下,你的 EnumSet 可以应用于最多不超过 64 个元素的 enum。如果 enum 超过了 64 个元素会发生什么呢?
+
+```java
+// enums/BigEnumSet.java
+import java.util.*;
+public class BigEnumSet {
+ enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,
+ A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,
+ A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,
+ A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,
+ A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,
+ A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,
+ A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,
+ A70, A71, A72, A73, A74, A75 }
+ public static void main(String[] args) {
+ EnumSet bigEnumSet = EnumSet.allOf(Big.class);
+ System.out.println(bigEnumSet);
+ }
+}
+```
+
+输出为:
+
+```java
+[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12,
+A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23,
+A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34,
+A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45,
+A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56,
+A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67,
+A68, A69, A70, A71, A72, A73, A74, A75]
+```
+
+显然,EnumSet 可以应用于多过 64 个元素的 enum,所以我猜测,Enum 会在必要的时候增加一个 long。
+
## 使用 EnumMap
+EnumMap 是一种特殊的 Map,它要求其中的键(key)必须来自一个 enum,由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快,我们可以放心地使用 enum 实例在 EnumMap 中进行查找操作。不过,我们只能将 enum 的实例作为键来调用 put() 可方法,其他操作与使用一般的 Map 差不多。
+
+下面的例子演示了*命令设计模式*的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们了:
+
+```java
+// enums/EnumMaps.java
+// Basics of EnumMaps
+// {java enums.EnumMaps}
+package enums;
+import java.util.*;
+import static enums.AlarmPoints.*;
+interface Command { void action(); }
+public class EnumMaps {
+ public static void main(String[] args) {
+ EnumMap em =
+ new EnumMap<>(AlarmPoints.class);
+ em.put(KITCHEN,
+ () -> System.out.println("Kitchen fire!"));
+ em.put(BATHROOM,
+ () -> System.out.println("Bathroom alert!"));
+ for(Map.Entry e:
+ em.entrySet()) {
+ System.out.print(e.getKey() + ": ");
+ e.getValue().action();
+ }
+ try { // If there's no value for a particular key:
+ em.get(UTILITY).action();
+ } catch(Exception e) {
+ System.out.println("Expected: " + e);
+ }
+ }
+}
+```
+
+输出为:
+
+```
+BATHROOM: Bathroom alert!
+KITCHEN: Kitchen fire!
+Expected: java.lang.NullPointerException
+```
+
+与 EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。
+
+main0 方法的最后部分说明,enum 的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用 put() 方法来存人相应的值的话,其对应的值就是 null。
+
+与常量相关的方法(constant-specific methods 将在下一节中介绍)相比,EnumMap 有一个优点,那 EnumMap 允许程序员改变值对象,而常量相关的方法在编译期就被固定了。稍后你会看到,在你有多种类型的 enum,而且它们之间存在互操作的情况下,我们可以用 EnumMap 实现多路分发(multiple dispatching)。
+
## 常量特定方法
+Java 的 enum 有一个非常有趣的特性,即它允许程序员为 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。要实现常量相关的方法,你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。参考下面的例子:
+
+```java
+// enums/ConstantSpecificMethod.java
+import java.util.*;
+import java.text.*;
+public enum ConstantSpecificMethod {
+ DATE_TIME {
+ @Override
+ String getInfo() {
+ return
+ DateFormat.getDateInstance()
+ .format(new Date());
+ }
+ },
+ CLASSPATH {
+ @Override
+ String getInfo() {
+ return System.getenv("CLASSPATH");
+ }
+ },
+ VERSION {
+ @Override
+ String getInfo() {
+ return System.getProperty("java.version");
+ }
+ };
+ abstract String getInfo();
+ public static void main(String[] args) {
+ for(ConstantSpecificMethod csm : values())
+ System.out.println(csm.getInfo());
+ }
+}
+```
+
+输出为:
+
+```java
+May 9, 2017
+C:\Users\Bruce\Documents\GitHub\on-
+java\ExtractedExamples\\gradle\wrapper\gradle-
+wrapper.jar
+1.8.0_112
+```
+
+通过相应的 enum 实例,我们可以调用其上的方法。这通常也称为表驱动的代码(table-driven code,请注意它与前面提到的命令模式的相似之处)。
+
+在面向对象的程序设计中,不同的行为与不同的类关联。而通过常量相关的方法,每个 enum 实例可以具备自己独特的行为,这似乎说明每个 enum 实例就像一个独特的类。在上面的例子中,enum 实例似乎被当作其“超类”ConstantSpecificMethod 来使用,在调用 getInfo() 方法时,体现出多态的行为。
+
+然而,enum 实例与类的相似之处也仅限于此了。我们并不能真的将 enum 实例作为一个类型来使用:
+
+```java
+// enums/NotClasses.java
+// {javap -c LikeClasses}
+enum LikeClasses {
+ WINKEN {
+ @Override
+ void behavior() {
+ System.out.println("Behavior1");
+ }
+ },
+ BLINKEN {
+ @Override
+ void behavior() {
+ System.out.println("Behavior2");
+ }
+ },
+ NOD {
+ @Override
+ void behavior() {
+ System.out.println("Behavior3");
+ }
+ };
+ abstract void behavior();
+}
+public class NotClasses {
+ // void f1(LikeClasses.WINKEN instance) {} // Nope
+}
+```
+
+输出为(前 12 行):
+
+```
+Compiled from "NotClasses.java"
+abstract class LikeClasses extends
+java.lang.Enum {
+public static final LikeClasses WINKEN;
+public static final LikeClasses BLINKEN;
+public static final LikeClasses NOD;
+public static LikeClasses[] values();
+Code:
+0: getstatic #2 // Field
+$VALUES:[LLikeClasses;
+3: invokevirtual #3 // Method
+"[LLikeClasses;".clone:()Ljava/lang/Object;
+...
+```
+
+在方法 f1() 中,编译器不允许我们将一个 enum 实例当作 class 类型。如果我们分析一下编译器生成的代码,就知道这种行为也是很正常的。因为每个 enum 元素都是一个 LikeClasses 类型的 static final 实例。
+
+同时,由于它们是 static 实例,无法访问外部类的非 static 元素或方法,所以对于内部的 enum 的实例而言,其行为与一般的内部类并不相同。
+
+再看一个更有趣的关于洗车的例子。每个顾客在洗车时,都有一个选择菜单,每个选择对应一个不同的动作。可以将一个常量相关的方法关联到一个选择上,再使用一个 EnumSet 来保存客户的选择:
+
+```java
+// enums/CarWash.java
+import java.util.*;
+public class CarWash {
+ public enum Cycle {
+ UNDERBODY {
+ @Override
+ void action() {
+ System.out.println("Spraying the underbody");
+ }
+ },
+ WHEELWASH {
+ @Override
+ void action() {
+ System.out.println("Washing the wheels");
+ }
+ },
+ PREWASH {
+ @Override
+ void action() {
+ System.out.println("Loosening the dirt");
+ }
+ },
+ BASIC {
+ @Override
+ void action() {
+ System.out.println("The basic wash");
+ }
+ },
+ HOTWAX {
+ @Override
+ void action() {
+ System.out.println("Applying hot wax");
+ }
+ },
+ RINSE {
+ @Override
+ void action() {
+ System.out.println("Rinsing");
+ }
+ },
+ BLOWDRY {
+ @Override
+ void action() {
+ System.out.println("Blowing dry");
+ }
+ };
+ abstract void action();
+ }
+ EnumSet cycles =
+ EnumSet.of(Cycle.BASIC, Cycle.RINSE);
+ public void add(Cycle cycle) {
+ cycles.add(cycle);
+ }
+ public void washCar() {
+ for(Cycle c : cycles)
+ c.action();
+ }
+ @Override
+ public String toString() {
+ return cycles.toString();
+ }
+ public static void main(String[] args) {
+ CarWash wash = new CarWash();
+ System.out.println(wash);
+ wash.washCar();
+// Order of addition is unimportant:
+ wash.add(Cycle.BLOWDRY);
+ wash.add(Cycle.BLOWDRY); // Duplicates ignored
+ wash.add(Cycle.RINSE);
+ wash.add(Cycle.HOTWAX);
+ System.out.println(wash);
+ wash.washCar();
+ }
+}
+```
+
+输出为:
+
+```
+[BASIC, RINSE]
+The basic wash
+Rinsing
+[BASIC, HOTWAX, RINSE, BLOWDRY]
+The basic wash
+Applying hot wax
+Rinsing
+Blowing dry
+```
+
+与使用匿名内部类相比较,定义常量相关方法的语法更高效、简洁。
+
+这个例子也展示了 EnumSet 了一些特性。因为它是一个集合,所以对于同一个元素而言,只能出现一次,因此对同一个参数重复地调用 add0 方法会被忽略掉(这是正确的行为,因为一个 bit 位开关只能“打开”一次),同样地,向 EnumSet 添加 enum 实例的顺序并不重要,因为其输出的次序决定于 enum 实例定义时的次序。
+
+除了实现 abstract 方法以外,程序员是否可以覆盖常量相关的方法呢?答案是肯定的,参考下面的程序:
+
+```java
+// enums/OverrideConstantSpecific.java
+public enum OverrideConstantSpecific {
+ NUT, BOLT,
+ WASHER {
+ @Override
+ void f() {
+ System.out.println("Overridden method");
+ }
+ };
+ void f() {
+ System.out.println("default behavior");
+ }
+ public static void main(String[] args) {
+ for(OverrideConstantSpecific ocs : values()) {
+ System.out.print(ocs + ": ");
+ ocs.f();
+ }
+ }
+}
+```
+
+输出为:
+
+```
+NUT: default behavior
+BOLT: default behavior
+WASHER: Overridden method
+```
+
+虽然 enum 有某些限制,但是一般而言,我们还是可以将其看作是类。
+
+### 使用 enum 的职责链
+
+在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。
+
+通过常量相关的方法,我们可以很容易地实现一个简单的职责链。我们以一个邮局的模型为例。邮局需要以尽可能通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件最终被确定为一封死信。其中的每一次尝试可以看作为一个策略(也是一个设计模式),而完整的处理方式列表就是一个职责链。
+
+我们先来描述一下邮件。邮件的每个关键特征都可以用 enum 来表示。程序将随机地生成 Mail 对象,如果要减小一封邮件的 GeneralDelivery 为 YES 的概率,那最简单的方法就是多创建几个不是 YES 的 enum 实例,所以 enum 的定义看起来有点古怪。
+
+我们看到 Mail 中有一个 randomMail() 方法,它负责随机地创建用于测试的邮件。而 generator() 方法生成一个 Iterable 对象,该对象在你调用 next() 方法时,在其内部使用 randomMail() 来创建 Mail 对象。这样的结构使程序员可以通过调用 Mail.generator() 方法,很容易地构造出一个 foreach 循环:
+
+```java
+// enums/PostOffice.java
+// Modeling a post office
+import java.util.*;
+import onjava.*;
+class Mail {
+ // The NO's reduce probability of random selection:
+ enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}
+ enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}
+ enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
+ enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}
+ enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}
+ GeneralDelivery generalDelivery;
+ Scannability scannability;
+ Readability readability;
+ Address address;
+ ReturnAddress returnAddress;
+ static long counter = 0;
+ long id = counter++;
+ @Override
+ public String toString() { return "Mail " + id; }
+ public String details() {
+ return toString() +
+ ", General Delivery: " + generalDelivery +
+ ", Address Scanability: " + scannability +
+ ", Address Readability: " + readability +
+ ", Address Address: " + address +
+ ", Return address: " + returnAddress;
+ }
+ // Generate test Mail:
+ public static Mail randomMail() {
+ Mail m = new Mail();
+ m.generalDelivery =
+ Enums.random(GeneralDelivery.class);
+ m.scannability =
+ Enums.random(Scannability.class);
+ m.readability =
+ Enums.random(Readability.class);
+ m.address = Enums.random(Address.class);
+ m.returnAddress =
+ Enums.random(ReturnAddress.class);
+ return m;
+ }
+ public static
+ Iterable generator(final int count) {
+ return new Iterable() {
+ int n = count;
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ @Override
+ public boolean hasNext() {
+ return n-- > 0;
+ }
+ @Override
+ public Mail next() {
+ return randomMail();
+ }
+ @Override
+ public void remove() { // Not implemented
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+}
+public class PostOffice {
+ enum MailHandler {
+ GENERAL_DELIVERY {
+ @Override
+ boolean handle(Mail m) {
+ switch(m.generalDelivery) {
+ case YES:
+ System.out.println(
+ "Using general delivery for " + m);
+ return true;
+ default: return false;
+ }
+ }
+ },
+ MACHINE_SCAN {
+ @Override
+ boolean handle(Mail m) {
+ switch(m.scannability) {
+ case UNSCANNABLE: return false;
+ default:
+ switch(m.address) {
+ case INCORRECT: return false;
+ default:
+ System.out.println(
+ "Delivering "+ m + " automatically");
+ return true;
+ }
+ }
+ }
+ },
+ VISUAL_INSPECTION {
+ @Override
+ boolean handle(Mail m) {
+ switch(m.readability) {
+ case ILLEGIBLE: return false;
+ default:
+ switch(m.address) {
+ case INCORRECT: return false;
+ default:
+ System.out.println(
+ "Delivering " + m + " normally");
+ return true;
+ }
+ }
+ }
+ },
+ RETURN_TO_SENDER {
+ @Override
+ boolean handle(Mail m) {
+ switch(m.returnAddress) {
+ case MISSING: return false;
+ default:
+ System.out.println(
+ "Returning " + m + " to sender");
+ return true;
+ }
+ }
+ };
+ abstract boolean handle(Mail m);
+ }
+ static void handle(Mail m) {
+ for(MailHandler handler : MailHandler.values())
+ if(handler.handle(m))
+ return;
+ System.out.println(m + " is a dead letter");
+ }
+ public static void main(String[] args) {
+ for(Mail mail : Mail.generator(10)) {
+ System.out.println(mail.details());
+ handle(mail);
+ System.out.println("*****");
+ }
+ }
+}
+```
+
+输出为:
+
+```
+Mail 0, General Delivery: NO2, Address Scanability:
+UNSCANNABLE, Address Readability: YES3, Address
+Address: OK1, Return address: OK1
+Delivering Mail 0 normally
+*****
+Mail 1, General Delivery: NO5, Address Scanability:
+YES3, Address Readability: ILLEGIBLE, Address Address:
+OK5, Return address: OK1
+Delivering Mail 1 automatically
+*****
+Mail 2, General Delivery: YES, Address Scanability:
+YES3, Address Readability: YES1, Address Address: OK1,
+Return address: OK5
+Using general delivery for Mail 2
+*****
+Mail 3, General Delivery: NO4, Address Scanability:
+YES3, Address Readability: YES1, Address Address:
+INCORRECT, Return address: OK4
+Returning Mail 3 to sender
+*****
+Mail 4, General Delivery: NO4, Address Scanability:
+UNSCANNABLE, Address Readability: YES1, Address
+Address: INCORRECT, Return address: OK2
+Returning Mail 4 to sender
+*****
+Mail 5, General Delivery: NO3, Address Scanability:
+YES1, Address Readability: ILLEGIBLE, Address Address:
+OK4, Return address: OK2
+Delivering Mail 5 automatically
+*****
+Mail 6, General Delivery: YES, Address Scanability:
+YES4, Address Readability: ILLEGIBLE, Address Address:
+OK4, Return address: OK4
+Using general delivery for Mail 6
+*****
+Mail 7, General Delivery: YES, Address Scanability:
+YES3, Address Readability: YES4, Address Address: OK2,
+Return address: MISSING
+Using general delivery for Mail 7
+*****
+Mail 8, General Delivery: NO3, Address Scanability:
+YES1, Address Readability: YES3, Address Address:
+INCORRECT, Return address: MISSING
+Mail 8 is a dead letter
+*****
+Mail 9, General Delivery: NO1, Address Scanability:
+UNSCANNABLE, Address Readability: YES2, Address
+Address: OK1, Return address: OK4
+Delivering Mail 9 normally
+*****
+```
+
+职责链由 enum MailHandler 实现,而 enum 定义的次序决定了各个解决策略在应用时的次序。对每一封邮件,都要按此顺序尝试每个解决策略,直到其中一个能够成功地处理该邮件,如果所有的策略都失败了,那么该邮件将被判定为一封死信。
+
+### 使用 enum 的状态机
+
+枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态(transient states),而一旦任务执行结束,状态机就会立刻离开瞬时状态。
+
+每个状态都具有某些可接受的输入,不同的输入会使状态机从当前状态转移到不同的新状态。由于 enum 对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。
+
+自动售贷机是一个很好的状态机的例子。首先,我们用一个 enum 定义各种输入:
+
+```java
+// enums/Input.java
+import java.util.*;
+public enum Input {
+ NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),
+ TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),
+ ABORT_TRANSACTION {
+ @Override
+ public int amount() { // Disallow
+ throw new RuntimeException("ABORT.amount()");
+ }
+ },
+ STOP { // This must be the last instance.
+ @Override
+ public int amount() { // Disallow
+ throw new RuntimeException("SHUT_DOWN.amount()");
+ }
+ };
+ int value; // In cents
+ Input(int value) { this.value = value; }
+ Input() {}
+ int amount() { return value; }; // In cents
+ static Random rand = new Random(47);
+ public static Input randomSelection() {
+ // Don't include STOP:
+ return values()[rand.nextInt(values().length - 1)];
+ }
+}
+```
+
+注意,除了两个特殊的 Input 实例之外,其他的 Input 都有相应的价格,因此在接口中定义了 amount(方法。然而,对那两个特殊 Input 实例而言,调用 amount(方法并不合适,所以如果程序员调用它们的 amount)方法就会有异常抛出(在接口内定义了一个方法,然后在你调用该方法的某个实现时就会抛出异常),这似乎有点奇怪,但由于 enum 的限制,我们不得不采用这种方式。
+
+VendingMachine 对输入的第一个反应是将其归类为 Category enum 中的某一个 enum 实例,这可以通过 switch 实现。下面的例子演示了 enum 是如何使代码变得更加清晰且易于管理的:
+
+```java
+// enums/VendingMachine.java
+// {java VendingMachine VendingMachineInput.txt}
+import java.util.*;
+import java.io.IOException;
+import java.util.function.*;
+import java.nio.file.*;
+import java.util.stream.*;
+enum Category {
+ MONEY(Input.NICKEL, Input.DIME,
+ Input.QUARTER, Input.DOLLAR),
+ ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS,
+ Input.SODA, Input.SOAP),
+ QUIT_TRANSACTION(Input.ABORT_TRANSACTION),
+ SHUT_DOWN(Input.STOP);
+ private Input[] values;
+ Category(Input... types) { values = types; }
+ private static EnumMap categories =
+ new EnumMap<>(Input.class);
+ static {
+ for(Category c : Category.class.getEnumConstants())
+ for(Input type : c.values)
+ categories.put(type, c);
+ }
+ public static Category categorize(Input input) {
+ return categories.get(input);
+ }
+}
+
+public class VendingMachine {
+ private static State state = State.RESTING;
+ private static int amount = 0;
+ private static Input selection = null;
+ enum StateDuration { TRANSIENT } // Tagging enum
+ enum State {
+ RESTING {
+ @Override
+ void next(Input input) {
+ switch(Category.categorize(input)) {
+ case MONEY:
+ amount += input.amount();
+ state = ADDING_MONEY;
+ break;
+ case SHUT_DOWN:
+ state = TERMINAL;
+ default:
+ }
+ }
+ },
+ ADDING_MONEY {
+ @Override
+ void next(Input input) {
+ switch(Category.categorize(input)) {
+ case MONEY:
+ amount += input.amount();
+ break;
+ case ITEM_SELECTION:
+ selection = input;
+ if(amount < selection.amount())
+ System.out.println(
+ "Insufficient money for " + selection);
+ else state = DISPENSING;
+ break;
+ case QUIT_TRANSACTION:
+ state = GIVING_CHANGE;
+ break;
+ case SHUT_DOWN:
+ state = TERMINAL;
+ default:
+ }
+ }
+ },
+ DISPENSING(StateDuration.TRANSIENT) {
+ @Override
+ void next() {
+ System.out.println("here is your " + selection);
+ amount -= selection.amount();
+ state = GIVING_CHANGE;
+ }
+ },
+ GIVING_CHANGE(StateDuration.TRANSIENT) {
+ @Override
+ void next() {
+ if(amount > 0) {
+ System.out.println("Your change: " + amount);
+ amount = 0;
+ }
+ state = RESTING;
+ }
+ },
+ TERMINAL {@Override
+ void output() { System.out.println("Halted"); } };
+ private boolean isTransient = false;
+ State() {}
+ State(StateDuration trans) { isTransient = true; }
+ void next(Input input) {
+ throw new RuntimeException("Only call " +
+ "next(Input input) for non-transient states");
+ }
+ void next() {
+ throw new RuntimeException(
+ "Only call next() for " +
+ "StateDuration.TRANSIENT states");
+ }
+ void output() { System.out.println(amount); }
+ }
+ static void run(Supplier gen) {
+ while(state != State.TERMINAL) {
+ state.next(gen.get());
+ while(state.isTransient)
+ state.next();
+ state.output();
+ }
+ }
+ public static void main(String[] args) {
+ Supplier gen = new RandomInputSupplier();
+ if(args.length == 1)
+ gen = new FileInputSupplier(args[0]);
+ run(gen);
+ }
+}
+
+// For a basic sanity check:
+class RandomInputSupplier implements Supplier {
+ @Override
+ public Input get() {
+ return Input.randomSelection();
+ }
+}
+
+// Create Inputs from a file of ';'-separated strings:
+class FileInputSupplier implements Supplier {
+ private Iterator input;
+ FileInputSupplier(String fileName) {
+ try {
+ input = Files.lines(Paths.get(fileName))
+ .skip(1) // Skip the comment line
+ .flatMap(s -> Arrays.stream(s.split(";")))
+ .map(String::trim)
+ .collect(Collectors.toList())
+ .iterator();
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ @Override
+ public Input get() {
+ if(!input.hasNext())
+ return null;
+ return Enum.valueOf(Input.class, input.next().trim());
+ }
+}
+```
+
+输出为:
+
+```
+25
+50
+75
+here is your CHIPS
+0
+100
+200
+here is your TOOTHPASTE
+0
+25
+35
+Your change: 35
+0
+25
+35
+Insufficient money for SODA
+35
+60
+70
+75
+Insufficient money for SODA
+75
+Your change: 75
+0
+Halted
+```
+
+由于用 switch 语句从 enum 实例中进行选择是最常见的一种方式(请注意,为了使 enum 在 switch 语句中的使用变得简单,我们是需要付出其他代价的),所以,我们经常遇到这样的问题:将多个 enum 进行分类时,“我们希望在什么 enum 中使用 switch 语句?”我们通过 VendingMachine 的例子来研究一下这个问题。对于每一个 State,我们都需要在输入动作的基本分类中进行查找:用户塞入钞票,选择了某个货物,操作被取消,以及机器停止。然而,在这些基本分类之下,我们又可以塞人不同类型的钞票,可以选择不同的货物。Category enum 将不同类型的 Input 进行分组,因而,可以使用 categorize0 方法为 switch 语句生成恰当的 Cateroy 实例。并且,该方法使用的 EnumMap 确保了在其中进行查询时的效率与安全。
+
+如果读者仔细研究 VendingMachine 类,就会发现每种状态的不同之处,以及对于输入的不同响应,其中还有两个瞬时状态。在 run() 方法中,状态机等待着下一个 Input,并一直在各个状态中移动,直到它不再处于瞬时状态。
+
+通过两种不同的 Generator 对象,我们可以使用不同的 Supplier 对象来测试 VendingMachine,首先是 RandomInputSupplier,它会不停地生成除了 SHUT-DOWN 之外的各种输入。通过长时间地运行 RandomInputSupplier,可以起到健全测试(sanity test)的作用,能够确保该状态机不会进入一个错误状态。另一个是 FileInputSupplier,使用文件以文本的方式来描述输入,然后将它们转换成 enum 实例,并创建对应的 Input 对象。上面的程序使用的正是如下的文本文件:
+
+```
+// enums/VendingMachineInput.txt
+QUARTER; QUARTER; QUARTER; CHIPS;
+DOLLAR; DOLLAR; TOOTHPASTE;
+QUARTER; DIME; ABORT_TRANSACTION;
+QUARTER; DIME; SODA;
+QUARTER; DIME; NICKEL; SODA;
+ABORT_TRANSACTION;
+STOP;
+```
+
+FileInputSupplier 构造函数将此文件转换为流,并跳过注释行。然后它使用 String.split() 以分号进行分割。这会生成一个 String 数组,并可以通过将其转换为 Stream,然后应用 flatMap() 来将其输入到流中。其输出结果将去除所有空格空格,并转换为 List\,且从中获取 Iterator\。
+
+这种设计有一个缺陷,它要求 enum State 实例访问的 VendingMachine 属性必须声明为 static,这意味着,你只能有一个 VendingMachine 实例。不过如果我们思考一下实际的(嵌入式 Java)应用,这也许并不是一个大问题,因为在一台机器上,我们可能只有一个应用程序。
-## 多次调度
+## 多路分发
+
+当你要处理多种交互类型时,程序可能会变得相当杂乱。举例来说,如果一个系统要分析和执行数学表达式。我们可能会声明 Number.plus(Number),Number.multiple(Number) 等等,其中 Number 是各种数字对象的超类。然而,当你声明 a.plus(b) 时,你并不知道 a 或 b 的确切类型,那你如何能让它们正确地交互呢?
+
+你可能从未思考过这个问题的答案.Java 只支持单路分发。也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么 Java 的动态绑定机制只能处理其中一个的类型。这就无法解决我们上面提到的问题。所以,你必须自己来判定其他的类型,从而实现自己的动态线定行为。
+
+解决上面问题的办法就是多路分发(在那个例子中,只有两个分发,一般称之为两路分发).多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知的类型。要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。一般而言,程序员需要有设定好的某种配置,以便一个方法调用能够引出更多的方法调用,从而能够在这个过程中处理多种类型。为了达到这种效果,我们需要与多个方法一同工作:因为每个分发都需要一个方法调用。在下面的例子中(实现了 “石头、剪刀、布”游戏,也称为 RoShamBo)对应的方法是 compete() 和 eval(),二者都是同一个类型的成员,它们可以产生三种 Outcome 实例中的一个作为结果:
+
+```java
+// enums/Outcome.java
+package enums;
+public enum Outcome { WIN, LOSE, DRAW }
+// enums/RoShamBo1.java
+// Demonstration of multiple dispatching
+// {java enums.RoShamBo1}
+package enums;
+ import java.util.*;
+ import static enums.Outcome.*;
+interface Item {
+ Outcome compete(Item it);
+ Outcome eval(Paper p);
+ Outcome eval(Scissors s);
+ Outcome eval(Rock r);
+}
+class Paper implements Item {
+ @Override
+ public Outcome compete(Item it) {
+ return it.eval(this);
+ }
+ @Override
+ public Outcome eval(Paper p) { return DRAW; }
+ @Override
+ public Outcome eval(Scissors s) { return WIN; }
+ @Override
+ public Outcome eval(Rock r) { return LOSE; }
+ @Override
+ public String toString() { return "Paper"; }
+}
+class Scissors implements Item {
+ @Override
+ public Outcome compete(Item it) {
+ return it.eval(this);
+ }
+ @Override
+ public Outcome eval(Paper p) { return LOSE; }
+ @Override
+ public Outcome eval(Scissors s) { return DRAW; }
+ @Override
+ public Outcome eval(Rock r) { return WIN; }
+ @Override
+ public String toString() { return "Scissors"; }
+}
+class Rock implements Item {
+ @Override
+ public Outcome compete(Item it) {
+ return it.eval(this);
+ }
+ @Override
+ public Outcome eval(Paper p) { return WIN; }
+ @Override
+ public Outcome eval(Scissors s) { return LOSE; }
+ @Override
+ public Outcome eval(Rock r) { return DRAW; }
+ @Override
+ public String toString() { return "Rock"; }
+}
+public class RoShamBo1 {
+ static final int SIZE = 20;
+ private static Random rand = new Random(47);
+ public static Item newItem() {
+ switch(rand.nextInt(3)) {
+ default:
+ case 0: return new Scissors();
+ case 1: return new Paper();
+ case 2: return new Rock();
+ }
+ }
+ public static void match(Item a, Item b) {
+ System.out.println(
+ a + " vs. " + b + ": " + a.compete(b));
+ }
+ public static void main(String[] args) {
+ for(int i = 0; i < SIZE; i++)
+ match(newItem(), newItem());
+ }
+}
+```
+
+输出为:
+
+```
+Rock vs. Rock: DRAW
+Paper vs. Rock: WIN
+Paper vs. Rock: WIN
+Paper vs. Rock: WIN
+Scissors vs. Paper: WIN
+Scissors vs. Scissors: DRAW
+Scissors vs. Paper: WIN
+Rock vs. Paper: LOSE
+Paper vs. Paper: DRAW
+Rock vs. Paper: LOSE
+Paper vs. Scissors: LOSE
+Paper vs. Scissors: LOSE
+Rock vs. Scissors: WIN
+Rock vs. Paper: LOSE
+Paper vs. Rock: WIN
+Scissors vs. Paper: WIN
+Paper vs. Scissors: LOSE
+Paper vs. Scissors: LOSE
+Paper vs. Scissors: LOSE
+Paper vs. Scissors: LOSE
+```
+
+Item 是这几种类型的接口,将会被用作多路分发。RoShamBo1.match() 有两个 Item 参数,通过调用 Item.compete90) 方法开始两路分发。要判定 a 的类型,分发机制会在 a 的实际类型的 compete(内部起到分发的作用。compete() 方法通过调用 eval() 来为另一个类型实现第二次分法。
+
+将自身(this)作为参数调用 evalo,能够调用重载过的 eval() 方法,这能够保留第一次分发的类型信息。当第二次分发完成时,你就能够知道两个 Item 对象的具体类型了。
+
+要配置好多路分发需要很多的工序,不过要记住,它的好处在于方法调用时的优雅的话法,这避免了在一个方法中判定多个对象的类型的丑陋代码,你只需说,“嘿,你们两个,我不在乎你们是什么类型,请你们自己交流!”不过,在使用多路分发前,请先明确,这种优雅的代码对你确实有重要的意义。
+
+### 使用 enum 分发
+
+直接将 RoShamBol.java 翻译为基于 enum 的版本是有问题的,因为 enum 实例不是类型,不能将 enum 实例作为参数的类型,所以无法重载 eval() 方法。不过,还有很多方式可以实现多路分发,并从 enum 中获益。
+
+一种方式是使用构造器来初始化每个 enum 实例,并以“一组”结果作为参数。这二者放在一块,形成了类似查询表的结构:
+
+```java
+// enums/RoShamBo2.java
+// Switching one enum on another
+// {java enums.RoShamBo2}
+package enums;
+import static enums.Outcome.*;
+public enum RoShamBo2 implements Competitor {
+ PAPER(DRAW, LOSE, WIN),
+ SCISSORS(WIN, DRAW, LOSE),
+ ROCK(LOSE, WIN, DRAW);
+ private Outcome vPAPER, vSCISSORS, vROCK;
+ RoShamBo2(Outcome paper,
+ Outcome scissors, Outcome rock) {
+ this.vPAPER = paper;
+ this.vSCISSORS = scissors;
+ this.vROCK = rock;
+ }
+ @Override
+ public Outcome compete(RoShamBo2 it) {
+ switch(it) {
+ default:
+ case PAPER: return vPAPER;
+ case SCISSORS: return vSCISSORS;
+ case ROCK: return vROCK;
+ }
+ }
+ public static void main(String[] args) {
+ RoShamBo.play(RoShamBo2.class, 20);
+ }
+}
+```
+
+输出为:
+
+```
+ROCK vs. ROCK: DRAW
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+PAPER vs. PAPER: DRAW
+PAPER vs. SCISSORS: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. SCISSORS: DRAW
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+ROCK vs. PAPER: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+```
+
+在 compete() 方法中,一旦两种类型都被确定了,那么唯一的操作就是返回结果 Outcome 然而,你可能还需要调用其他的方法,(例如)甚至是调用在构造器中指定的某个命令对象上的方法。
+
+RoShamBo2.javal 之前的例子短小得多,而且更直接,更易于理解。注意,我们仍然是使用两路分发来判定两个对象的类型。在 RoShamBol.java 中,两次分发都是通过实际的方法调用实现,而在这个例子中,只有第一次分发是实际的方法调用。第二个分发使用的是 switch,不过这样做是安全的,因为 enum 限制了 switch 语句的选择分支。
+
+在代码中,enum 被单独抽取出来,因此它可以应用在其他例子中。首先,Competitor 接口定义了一种类型,该类型的对象可以与另一个 Competitor 相竞争:
+
+```java
+// enums/Competitor.java
+// Switching one enum on another
+package enums;
+public interface Competitor> {
+ Outcome compete(T competitor);
+}
+```
+
+然后,我们定义两个 static 方法(static 可以避免显式地指明参数类型),第一个是 match() 方法,它会为一个 Competitor 对象调用 compete() 方法,并与另一个 Competitor 对象作比较。在这个例子中,我们看到,match())方法的参数需要是 Competitor\ 类型。但是在 play() 方法中,类型参数必须同时是 Enum\ 类型(因为它将在 Enums.random() 中使用)和 Competitor\ 类型因为它将被传递给 match() 方法):
+
+```java
+// enums/RoShamBo.java
+// Common tools for RoShamBo examples
+package enums;
+import onjava.*;
+public class RoShamBo {
+ public static >
+ void match(T a, T b) {
+ System.out.println(
+ a + " vs. " + b + ": " + a.compete(b));
+ }
+ public static & Competitor>
+ void play(Class rsbClass, int size) {
+ for(int i = 0; i < size; i++)
+ match(Enums.random(rsbClass),Enums.random(rsbClass));
+ }
+}
+```
+
+play() 方法没有将类型参数 T 作为返回值类型,因此,似乎我们应该在 Class\ 中使用通配符来代替上面的参数声明。然而,通配符不能扩展多个基类,所以我们必须采用以上的表达式。
+
+### 使用常量相关的方法
+
+常量相关的方法允许我们为每个 enum 实例提供方法的不同实现,这使得常量相关的方法似乎是实现多路分发的完美解决方案。不过,通过这种方式,enum 实例虽然可以具有不同的行为,但它们仍然不是类型,不能将其作为方法签名中的参数类型来使用。最好的办法是将 enum 用在 switch 语句中,见下例:
+
+```java
+// enums/RoShamBo3.java
+// Using constant-specific methods
+// {java enums.RoShamBo3}
+package enums;
+import static enums.Outcome.*;
+public enum RoShamBo3 implements Competitor {
+ PAPER {
+ @Override
+ public Outcome compete(RoShamBo3 it) {
+ switch(it) {
+ default: // To placate the compiler
+ case PAPER: return DRAW;
+ case SCISSORS: return LOSE;
+ case ROCK: return WIN;
+ }
+ }
+ },
+ SCISSORS {
+ @Override
+ public Outcome compete(RoShamBo3 it) {
+ switch(it) {
+ default:
+ case PAPER: return WIN;
+ case SCISSORS: return DRAW;
+ case ROCK: return LOSE;
+ }
+ }
+ },
+ ROCK {
+ @Override
+ public Outcome compete(RoShamBo3 it) {
+ switch(it) {
+ default:
+ case PAPER: return LOSE;
+ case SCISSORS: return WIN;
+ case ROCK: return DRAW;
+ }
+ }
+ };
+ @Override
+ public abstract Outcome compete(RoShamBo3 it);
+ public static void main(String[] args) {
+ RoShamBo.play(RoShamBo3.class, 20);
+ }
+}
+```
+
+输出为:
+
+```
+ROCK vs. ROCK: DRAW
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+PAPER vs. PAPER: DRAW
+PAPER vs. SCISSORS: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. SCISSORS: DRAW
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+ROCK vs. PAPER: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+```
+
+虽然这种方式可以工作,但是却不甚合理,如果采用 RoShamB02.java 的解决方案,那么在添加一个新的类型时,只需更少的代码,而且也更直接。
+
+:然而,RoShamBo3.java 还可以压缩简化一下:
+
+```java
+// enums/RoShamBo4.java
+// {java enums.RoShamBo4}
+package enums;
+public enum RoShamBo4 implements Competitor {
+ ROCK {
+ @Override
+ public Outcome compete(RoShamBo4 opponent) {
+ return compete(SCISSORS, opponent);
+ }
+ },
+ SCISSORS {
+ @Override
+ public Outcome compete(RoShamBo4 opponent) {
+ return compete(PAPER, opponent);
+ }
+ },
+ PAPER {
+ @Override
+ public Outcome compete(RoShamBo4 opponent) {
+ return compete(ROCK, opponent);
+ }
+ };
+ Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {
+ return ((opponent == this) ? Outcome.DRAW
+ : ((opponent == loser) ? Outcome.WIN
+ : Outcome.LOSE));
+ }
+ public static void main(String[] args) {
+ RoShamBo.play(RoShamBo4.class, 20);
+ }
+}
+```
+
+输出为:
+
+```
+PAPER vs. PAPER: DRAW
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+ROCK vs. SCISSORS: WIN
+ROCK vs. ROCK: DRAW
+ROCK vs. SCISSORS: WIN
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. SCISSORS: DRAW
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+PAPER vs. ROCK: WIN
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. PAPER: WIN
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+```
+
+其中,具有两个参数的 compete() 方法执行第二个分发,该方法执行一系列的比较,其行为类似 switch 语句。这个版本的程序更简短,不过却比较难理解,对于一个大型系统而言,难以理解的代码将导致整个系统不够健壮。
+
+### 使用 EnumMap 进行分发
+
+使用 EnumMap 能够实现“真正的”两路分发。EnumMap 是为 enum 专门设计的一种性能非常好的特殊 Map。由于我们的目的是摸索出两种未知的类型,所以可以用一个 EnumMap 的 EnumMap 来实现两路分发:
+
+```java
+// enums/RoShamBo5.java
+// Multiple dispatching using an EnumMap of EnumMaps
+// {java enums.RoShamBo5}
+package enums;
+import java.util.*;
+import static enums.Outcome.*;
+enum RoShamBo5 implements Competitor {
+ PAPER, SCISSORS, ROCK;
+ static EnumMap>
+ table = new EnumMap<>(RoShamBo5.class);
+ static {
+ for(RoShamBo5 it : RoShamBo5.values())
+ table.put(it, new EnumMap<>(RoShamBo5.class));
+ initRow(PAPER, DRAW, LOSE, WIN);
+ initRow(SCISSORS, WIN, DRAW, LOSE);
+ initRow(ROCK, LOSE, WIN, DRAW);
+ }
+ static void initRow(RoShamBo5 it,
+ Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
+ EnumMap row =
+ RoShamBo5.table.get(it);
+ row.put(RoShamBo5.PAPER, vPAPER);
+ row.put(RoShamBo5.SCISSORS, vSCISSORS);
+ row.put(RoShamBo5.ROCK, vROCK);
+ }
+ @Override
+ public Outcome compete(RoShamBo5 it) {
+ return table.get(this).get(it);
+ }
+ public static void main(String[] args) {
+ RoShamBo.play(RoShamBo5.class, 20);
+ }
+}
+```
+
+输出为:
+
+```
+ROCK vs. ROCK: DRAW
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+PAPER vs. PAPER: DRAW
+PAPER vs. SCISSORS: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. SCISSORS: DRAW
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+ROCK vs. PAPER: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+```
+
+该程序在一个 static 子句中初始化 EnumMap 对象,具体见表格似的 initRow() 方法调用。请注意 compete() 方法,您可以看到,在一行语句中发生了两次分发。
+
+### 使用二维数组
+
+我们还可以进一步简化实现两路分发的解决方案。我们注意到,每个 enum 实例都有一个固定的值(基于其声明的次序),并且可以通过 ordinal() 方法取得该值。因此我们可以使用二维数组,将竞争者映射到竞争结果。采用这种方式能够获得最简洁、最直接的解决方案(很可能也是最快速的,虽然我们知道 EnumMap 内部其实也是使用数组实现的)。
+
+```java
+We can simplify the solution even more by noting that each enum instance has a fixed
+ value (based on its declaration order) and that ordinal() produces this value. A two-
+ dimensional array mapping the competitors onto the outcomes produces the smallest
+ and most straightforward solution (and possibly the fastest, although remember that
+ EnumMap uses an internal array):
+// enums/RoShamBo6.java
+// Enums using "tables" instead of multiple dispatch
+// {java enums.RoShamBo6}
+ package enums;
+ import static enums.Outcome.*;
+enum RoShamBo6 implements Competitor {
+ PAPER, SCISSORS, ROCK;
+ private static Outcome[][] table = {
+ { DRAW, LOSE, WIN }, // PAPER
+ { WIN, DRAW, LOSE }, // SCISSORS
+ { LOSE, WIN, DRAW }, // ROCK
+ };
+ @Override
+ public Outcome compete(RoShamBo6 other) {
+ return table[this.ordinal()][other.ordinal()];
+ }
+ public static void main(String[] args) {
+ RoShamBo.play(RoShamBo6.class, 20);
+ }
+}
+```
+
+输出为:
+
+```
+ROCK vs. ROCK: DRAW
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+PAPER vs. PAPER: DRAW
+PAPER vs. SCISSORS: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. SCISSORS: DRAW
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+ROCK vs. PAPER: LOSE
+ROCK vs. SCISSORS: WIN
+SCISSORS vs. ROCK: LOSE
+PAPER vs. SCISSORS: LOSE
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+SCISSORS vs. PAPER: WIN
+```
+
+table 与前一个例子中 initRow() 方法的调用次序完全相同。
+
+与前面一个例子相比,这个程序代码虽然简短,但表达能力却更强,部分原因是其代码更易于理解与修改,而且也更直接。不过,由于它使用的是数组,所以这种方式不太“安全”。如果使用一个大型数组,可能会不小心使用了错误的尺寸,而且,如果你的测试不能覆盖所有的可能性,有些错误可能会从你眼前溜过。
+
+事实上,以上所有的解决方案只是各种不同类型的表罢了。不过,分析各种表的表现形式,找出最适合的那一种,还是很有价值的。注意,虽然上例是最简洁的一种解决方案,但它也是相当僵硬的方案,因为它只能针对给定的常量输入产生常量输出。然而,也没有什么特别的理由阻止你用 table 来生成功能对象。对于某类问题而言,“表驱动式编码”的概念具有非常强大的功能。
+
## 本章小结
+虽然枚举类型本身并不是特别复杂,但我还是将本章安排在全书比较靠后的位置,这是因为,程序员可以将 enum 与 Java 语言的其他功能结合使用,例如多态、泛型和反射。
+
+虽然 Java 中的枚举比 C 或 C++中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。而本章正好说明了一个“小”功能所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。正如我在本书中一再强调的那样,优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。
+
+关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 ierator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。
+
From b5ddc2ab2a3794a3fc356292dde578ef8ffab93a Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Sat, 10 Aug 2019 23:19:02 +0800
Subject: [PATCH 013/449] =?UTF-8?q?=E6=9E=9A=E4=B8=BE=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
枚举更新完成
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index fdd8b68c..68d1f2bb 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@
- [ ] [第十九章 类型信息](docs/book/19-Type-Information.md)
- [ ] [第二十章 泛型](docs/book/20-Generics.md)
- [ ] [第二十一章 数组](docs/book/21-Arrays.md)
-- [ ] [第二十二章 枚举](docs/book/22-Enumerations.md)
+- [x] [第二十二章 枚举](docs/book/22-Enumerations.md)
- [x] [第二十三章 注解](docs/book/23-Annotations.md)
- [ ] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md)
- [ ] [第二十五章 设计模式](docs/book/25-Patterns.md)
From 1dc72318f6511904706015c83694f10495e554a8 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Sat, 10 Aug 2019 23:33:11 +0800
Subject: [PATCH 014/449] =?UTF-8?q?=E9=99=84=E5=BD=95:=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?=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 68d1f2bb..358234fd 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@
- [x] [附录:新IO](docs/book/Appendix-New-IO.md)
- [ ] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md)
- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md)
-- [ ] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md)
+- [x] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md)
- [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md)
- [ ] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md)
- [ ] [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md)
From 0d88465b8b189009a843383db5915be420c602e3 Mon Sep 17 00:00:00 2001
From: zhangzw
Date: Sun, 11 Aug 2019 11:03:59 +0800
Subject: [PATCH 015/449] Terminating Long-Running Tasks
---
docs/book/24-Concurrent-Programming.md | 73 ++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md
index 436c9521..b8190734 100644
--- a/docs/book/24-Concurrent-Programming.md
+++ b/docs/book/24-Concurrent-Programming.md
@@ -1192,9 +1192,82 @@ Lambda2
```
这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,您可以在后两次调用的情况下提取结果。
+
## 终止耗时任务
+并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。您经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当您关闭程序时。
+
+最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为您必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。
+
+InterruptedException,因为设计的向后兼容性残留。
+
+任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的shutdown进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。
+
+以这种方式终止任务听起来很简单:设置任务可以看到的**boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。
+
+在研究Java文献时,你会发现很多解决这个问题的方法,经常使用**volatile**关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发](./Appendix-Low-Level-Concurrency.md)中有所涉及。
+
+Java 5引入了**Atomic**类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加**AtomicBoolean**标志,告诉任务清理自己并退出。
+
+```java
+// concurrent/QuittableTask.java
+import java.util.concurrent.atomic.AtomicBoolean;import onjava.Nap;
+public class QuittableTask implements Runnable {
+ final int id;
+ public QuittableTask(int id) {
+ this.id = id;
+ }
+ private AtomicBoolean running =
+ new AtomicBoolean(true);
+ public void quit() {
+ running.set(false);
+ }
+ @Override
+ public void run() {
+ while(running.get()) // [1]
+ new Nap(0.1);
+ System.out.print(id + " "); // [2]
+ }
+}
+
+```
+
+虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。
+
+- [1]:只要运行标志为true,此任务的run()方法将继续。
+- [2]: 显示仅在任务退出时发生。
+
+需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,您没有任何反馈来告诉您已经做错了。通常,您编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。
+
+作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值
+
+```java
+// concurrent/QuittingTasks.java
+import java.util.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+import onjava.Nap;
+public class QuittingTasks {
+ public static final int COUNT = 150;
+ public static void main(String[] args) {
+ ExecutorService es =
+ Executors.newCachedThreadPool();
+ List tasks =
+ IntStream.range(1, COUNT)
+ .mapToObj(QuittableTask::new)
+ .peek(qt -> es.execute(qt))
+ .collect(Collectors.toList());
+ new Nap(1);
+ tasks.forEach(QuittableTask::quit); es.shutdown();
+ }
+}
+/* Output:24 27 31 8 11 7 19 12 16 4 23 3 28 32 15 20 63 60 68 6764 39 47 52 51 55 40 43 48 59 44 56 36 35 71 72 83 10396 92 88 99 100 87 91 79 75 84 76 115 108 112 104 107111 95 80 147 120 127 119 123 144 143 116 132 124 128
+136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97
+*/
+```
+
+我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。
## CompletableFuture类
From 0c08356b99f70045aea7dc0ecf93567c4101be96 Mon Sep 17 00:00:00 2001
From: zhangzw
Date: Sun, 11 Aug 2019 16:18:58 +0800
Subject: [PATCH 016/449] CompletableFuture1
---
docs/book/24-Concurrent-Programming.md | 158 +++++++++++++++++++++++++
1 file changed, 158 insertions(+)
diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md
index b8190734..4bdd7294 100644
--- a/docs/book/24-Concurrent-Programming.md
+++ b/docs/book/24-Concurrent-Programming.md
@@ -1272,11 +1272,169 @@ public class QuittingTasks {
## CompletableFuture类
+作为介绍,这里是使用CompletableFutures在QuittingTasks.java中:
+
+```java
+// concurrent/QuittingCompletable.java
+import java.util.*;
+import java.util.stream.*;
+import java.util.concurrent.*;
+import onjava.Nap;
+public class QuittingCompletable {
+ public static void main(String[] args) {
+ List tasks =
+ IntStream.range(1, QuittingTasks.COUNT)
+ .mapToObj(QuittableTask::new)
+ .collect(Collectors.toList());
+ List> cfutures =
+ tasks.stream()
+ .map(CompletableFuture::runAsync)
+ .collect(Collectors.toList());
+ new Nap(1);
+ tasks.forEach(QuittableTask::quit);
+ cfutures.forEach(CompletableFuture::join);
+ }
+}
+/* Output:7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3*/
+```
+
+任务是一个**List **,就像在**QuittingTasks.java**中一样,但是在这个例子中,没有**peek()**将每个**QuittableTask**提交给**ExecutorService**。相反,在创建cfutures期间,每个任务都交给**CompletableFuture::runAsync**。这执行**VerifyTask.run(**)并返回**CompletableFuture **。因为**run()**不返回任何内容,所以在这种情况下我只使用**CompletableFuture**调用**join()**来等待它完成。
+
+在此示例中需要注意的重要事项是,运行任务不需要**ExecutorService**。这由**CompletableFuture**管理(尽管有提供自己的**ExecutorService**的选项)。你也不需要调用**shutdown()**;事实上,除非你像我这样明确地调用**join()**,程序将尽快退出,而不必等待任务完成。
+
+这个例子只是一个起点。你很快就会看到ComplempleFutures能够做得更多。
+
+### 基本用法
+
+这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作:
+
+```java
+// concurrent/Machina.java
+import onjava.Nap;
+public class Machina {
+ public enum State {
+ START, ONE, TWO, THREE, END;
+ State step() {
+ if(equals(END))
+ return END;
+ return values()[ordinal() + 1];
+ }
+ }
+ private State state = State.START;
+ private final int id;
+ public Machina(int id) {
+ this.id = id;
+ }
+ public static Machina work(Machina m) {
+ if(!m.state.equals(State.END)){
+ new Nap(0.1);
+ m.state = m.state.step();
+ }
+ System.out.println(m);return m;
+ }
+ @Override
+ public StringtoString() {
+ return"Machina" + id + ": " + (state.equals(State.END)? "complete" : state);
+ }
+}
+
+```
+
+这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。
+
+我们可以用**CompletableFuture**做的一件事是使用**completedFuture()**将它包装在感兴趣的对象中
+
+```java
+// concurrent/CompletedMachina.java
+import java.util.concurrent.*;
+public class CompletedMachina {
+ public static void main(String[] args) {
+ CompletableFuture cf =
+ CompletableFuture.completedFuture(
+ new Machina(0));
+ try {
+ Machina m = cf.get(); // Doesn't block
+ } catch(InterruptedException |
+ ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+
+**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。
+
+通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。
+
+当我们将Machina包装在CompletableFuture中时,我们发现我们可以在CompletableFuture上添加操作来处理所包含的对象,事情变得更加有趣:
+
+```java
+// concurrent/CompletableApply.java
+import java.util.concurrent.*;
+public class CompletableApply {
+ public static void main(String[] args) {
+ CompletableFuture cf =
+ CompletableFuture.completedFuture(
+ new Machina(0));
+ CompletableFuture cf2 =
+ cf.thenApply(Machina::work);
+ CompletableFuture cf3 =
+ cf2.thenApply(Machina::work);
+ CompletableFuture cf4 =
+ cf3.thenApply(Machina::work);
+ CompletableFuture cf5 =
+ cf4.thenApply(Machina::work);
+ }
+}
+/* Output:
+Machina0: ONE
+Machina0: TWO
+Machina0: THREE
+Machina0: complete
+*/
+
+```
+
+**thenApply()**应用一个接受输入并产生输出的函数。在这种情况下,**work()**函数产生与它相同的类型,因此每个得到的**CompletableFuture**仍然被输入为**Machina**,但是(类似于**Streams**中的**map()**)**Function**也可以返回不同的类型,这将反映在返回类型
+
+您可以在此处看到有关CompletableFutures的重要信息:它们会在您执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。
+
+我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样:
+
+```java
+// concurrent/CompletableApplyChained.javaimport java.util.concurrent.*;
+import onjava.Timer;
+public class CompletableApplyChained {
+ public static void main(String[] args) {
+ Timer timer = new Timer();
+ CompletableFuture cf =
+ CompletableFuture.completedFuture(
+ new Machina(0))
+ .thenApply(Machina::work)
+ .thenApply(Machina::work)
+ .thenApply(Machina::work)
+ .thenApply(Machina::work);
+ System.out.println(timer.duration());
+ }
+}
+/* Output:
+Machina0: ONE
+Machina0: TWO
+Machina0: THREE
+Machina0: complete
+514
+*/
+
+```
+
+在这里,我们还添加了一个**Timer**,它向我们展示每一步增加100毫秒,还有一些额外的开销。
+**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。
## 死锁
+
## 构造函数非线程安全
From ea477ce90a76374dc29fcfbd828720bd61f122a4 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Sun, 11 Aug 2019 20:46:08 +0800
Subject: [PATCH 017/449] [hotfix] fix some minor errors
---
docs/book/12-Collections.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 199300a8..799ac53e 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -68,7 +68,7 @@ ndOrangesWithoutGenerics.java:23)
在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。
-在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
+在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。
通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:
```java
@@ -94,17 +94,17 @@ public class ApplesAndOrangesWithGenerics {
*/
```
-在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
+在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:
```java
ArrayList apples = new ArrayList();
```
-随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。Java语言团队采纳了这种*类型推断*(type inference)的请求,即使只是这么小的一点。
+随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。虽然*类型推断*(type inference)只是个很小的请求,Java 语言团队仍然欣然接受并进行了改进。
有了 **ArrayList** 声明中的类型指定,编译器会阻止将 **Orange** 放入 **apples** ,因此,这会成为一个编译期错误而不是运行时错误。
-使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。这样,不仅可以知道编译器将会检查放入集合中的对象的类型,而且在使用集合中的对象时,可以使用更加清晰的语法。
+使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。
当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:
```java
From 42e88620847e51990c9ee11bada20e89fdff7694 Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Mon, 12 Aug 2019 10:38:16 +0800
Subject: [PATCH 018/449] [hotfix] fix a typo
---
docs/book/12-Collections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 799ac53e..a77c16bd 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -459,7 +459,7 @@ sub: [Mouse, Pug]
`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
-第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间,而不仅仅只能用 **Collection** 的 `addAll()` 方法将追加到列表末尾。
+第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间位置,而不是仅能用 **Collection** 的 `addAll()` 方法将其追加到列表末尾。
第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。
From 64a7648b74ffd1f621a2d6d3368983a40dff3a7c Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Mon, 12 Aug 2019 12:24:55 +0800
Subject: [PATCH 019/449] fix a typo
---
docs/book/12-Collections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index a77c16bd..24dfbf84 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -459,7 +459,7 @@ sub: [Mouse, Pug]
`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。
-第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间位置,而不是仅能用 **Collection** 的 `addAll()` 方法将其追加到列表末尾。
+第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间位置,而不是仅能用 **Collection** 的 `addAll()` 方法将其追加到列表的末尾。
第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。
From c68e16ec1c046c9878a009472ab6e917233df5d3 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Mon, 12 Aug 2019 22:12:01 +0800
Subject: [PATCH 020/449] Update 07-Implementation-Hiding.md
---
docs/book/07-Implementation-Hiding.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md
index 96049c4a..e8224d61 100644
--- a/docs/book/07-Implementation-Hiding.md
+++ b/docs/book/07-Implementation-Hiding.md
@@ -48,7 +48,7 @@ public class SingleImport {
}
```
-现在你就可以不加限定词,直接使用 **ArrayList** 了。但是对于 **java.util** 包下的其他类,你还是不能用。要导入其中所有的类,只需使用 ***** ,就像本书中其他示例那样:
+现在你就可以不加限定词,直接使用 **ArrayList** 了。但是对于 **java.util** 包下的其他类,你还是不能用。要导入其中所有的类,只需使用 **\*** ,就像本书中其他示例那样:
```java
import java.util.*
```
@@ -201,7 +201,7 @@ com.mindviewinc.simple.List
### 冲突
-如果通过 ***** 导入了两个包含相同名字类名的类库,会发生什么?例如,假设程序如下:
+如果通过 **\*** 导入了两个包含相同名字类名的类库,会发生什么?例如,假设程序如下:
```java
import com.mindviewinc.simple.*;
From 0c1674dabc4be3bea468ff8d2757e8436e974dc7 Mon Sep 17 00:00:00 2001
From: Dragon Dove <874898731@qq.com>
Date: Mon, 12 Aug 2019 23:11:44 +0800
Subject: [PATCH 021/449] =?UTF-8?q?=E5=AE=8C=E6=88=90=E2=80=9C=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E2=80=9D=E7=AB=A0=E8=8A=82=E7=BF=BB=E8=AF=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/17-Files.md | 195 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 195 insertions(+)
diff --git a/docs/book/17-Files.md b/docs/book/17-Files.md
index 4ed54b63..ae32b905 100644
--- a/docs/book/17-Files.md
+++ b/docs/book/17-Files.md
@@ -644,15 +644,210 @@ evt.kind(): ENTRY_DELETE
## 文件查找
+到目前为止,为了找到文件,我们一直使用相当粗糙的方法,在 `path` 上调用 `toString()`,然后使用 `string` 操作查看结果。事实证明,`java.nio.file` 有更好的解决方案:通过在 `FileSystem` 对象上调用 `getPathMatcher()` 获得一个 `PathMatcher`,然后传入您感兴趣的模式。模式有两个选项:`glob` 和 `regex`。`glob` 比较简单,实际上功能非常强大,因此您可以使用 `glob` 解决许多问题。如果您的问题更复杂,可以使用 `regex`,这将在接下来的 `Strings` 一章中解释。
+在这里,我们使用 `glob` 查找以 `.tmp` 或 `.txt` 结尾的所有 `Path`:
+
+```java
+// files/Find.java
+// {ExcludeFromGradle}
+import java.nio.file.*;
+
+public class Find {
+ public static void
+ main(String[] args) throws Exception {
+ Path test = Paths.get("test");
+ Directories.refreshTestDir();
+ Directories.populateTestDir();
+ // Creating a *directory*, not a file:
+ Files.createDirectory(test.resolve("dir.tmp"));
+
+ PathMatcher matcher = FileSystems.getDefault()
+ .getPathMatcher("glob:**/*.{tmp,txt}");
+ Files.walk(test)
+ .filter(matcher::matches)
+ .forEach(System.out::println);
+ System.out.println("***************");
+
+ PathMatcher matcher2 = FileSystems.getDefault()
+ .getPathMatcher("glob:*.tmp");
+ Files.walk(test)
+ .map(Path::getFileName)
+ .filter(matcher2::matches)
+ .forEach(System.out::println);
+ System.out.println("***************");
+
+ Files.walk(test) // Only look for files
+ .filter(Files::isRegularFile)
+ .map(Path::getFileName)
+ .filter(matcher2::matches)
+ .forEach(System.out::println);
+ }
+}
+/* Output:
+test\bag\foo\bar\baz\5208762845883213974.tmp
+test\bag\foo\bar\baz\File.txt
+test\bar\baz\bag\foo\7918367201207778677.tmp
+test\bar\baz\bag\foo\File.txt
+test\baz\bag\foo\bar\8016595521026696632.tmp
+test\baz\bag\foo\bar\File.txt
+test\dir.tmp
+test\foo\bar\baz\bag\5832319279813617280.tmp
+test\foo\bar\baz\bag\File.txt
+***************
+5208762845883213974.tmp
+7918367201207778677.tmp
+8016595521026696632.tmp
+dir.tmp
+5832319279813617280.tmp
+***************
+5208762845883213974.tmp
+7918367201207778677.tmp
+8016595521026696632.tmp
+5832319279813617280.tmp
+*/
+```
+
+在 `matcher` 中,`glob` 表达式开头的 `**/` 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 `Path` 时非常有用。单 `*` 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性---我们正在寻找以`.tmp` 或 `.txt` 结尾的东西。您可以在 `getPathMatcher()` 文档中找到更多详细信息。
+
+`matcher2` 只使用 `*.tmp`,通常不匹配任何内容,但是添加 `map()` 操作会将完整路径减少到末尾的名称。
+
+注意,在这两种情况下,输出中都会出现 `dir.tmp`,即使它是一个目录而不是一个文件。要只查找文件,必须像在最后 `files.walk()` 中那样对其进行筛选。
## 文件读写
+此时,我们可以对路径和目录做任何事情。 现在让我们看一下操纵文件本身的内容。
+
+如果一个文件很“小”,也就是说“它运行得足够快且占用内存小”,那么 `java.nio.file.Files` 类中的实用程序将帮助你轻松读写文本和二进制文件。
+
+`Files.readAllLines()` 一次读取整个文件(因此,“小”文件很有必要),产生一个`List`。 对于示例文件,我们将重用`streams/Cheese.dat`:
+
+```java
+// files/ListOfLines.java
+import java.util.*;
+import java.nio.file.*;
+
+public class ListOfLines {
+ public static void
+ main(String[] args) throws Exception {
+ Files.readAllLines(
+ Paths.get("../streams/Cheese.dat"))
+ .stream()
+ .filter(line -> !line.startsWith("//"))
+ .map(line ->
+ line.substring(0, line.length()/2))
+ .forEach(System.out::println);
+ }
+}
+/* Output:
+Not much of a cheese
+Finest in the
+And what leads you
+Well, it's
+It's certainly uncon
+*/
+```
+
+跳过注释行,其余的内容每行只打印一半。 这实现起来很简单:你只需将 `Path` 传递给 `readAllLines()` (以前的 java 实现这个功能很复杂)。 有一个 `readAllLines()` 的重载版本,它包含一个 `Charset` 参数来存储文件的Unicode编码。
+
+`Files.write()` 被重载以写入 `byte` 数组或任何 `Iterable` 对象(它也有 `Charset` 选项):
+
+```java
+// files/Writing.java
+import java.util.*;
+import java.nio.file.*;
+
+public class Writing {
+ static Random rand = new Random(47);
+ static final int SIZE = 1000;
+ public static void
+ main(String[] args) throws Exception {
+ // Write bytes to a file:
+ byte[] bytes = new byte[SIZE];
+ rand.nextBytes(bytes);
+ Files.write(Paths.get("bytes.dat"), bytes);
+ System.out.println("bytes.dat: " +
+ Files.size(Paths.get("bytes.dat")));
+
+ // Write an iterable to a file:
+ List lines = Files.readAllLines(
+ Paths.get("../streams/Cheese.dat"));
+ Files.write(Paths.get("Cheese.txt"), lines);
+ System.out.println("Cheese.txt: " +
+ Files.size(Paths.get("Cheese.txt")));
+ }
+}
+/* Output:
+bytes.dat: 1000
+Cheese.txt: 199
+*/
+```
+
+我们使用 `Random` 来创建一个随机的 `byte` 数组; 你可以看到生成的文件大小是1000。
+
+一个 `List` 被写入文件,任何 `Iterable` 对象也可以这么做。
+
+如果文件大小有问题怎么办? 比如说:
+
+1.文件太大,如果你一次读完整个文件,你可能会耗尽内存。
+
+2.您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。
+
+`Files.lines()` 方便地将文件转换为行的 `Stream`:
+
+```java
+// files/ReadLineStream.java
+import java.nio.file.*;
+
+public class ReadLineStream {
+ public static void
+ main(String[] args) throws Exception {
+ Files.lines(Paths.get("PathInfo.java"))
+ .skip(13)
+ .findFirst()
+ .ifPresent(System.out::println);
+ }
+}
+/* Output:
+ show("RegularFile", Files.isRegularFile(p));
+*/
+```
+
+这本章中的第一个流式传输的示例,跳过13行,然后选择下一行并将其打印出来。
+
+`Files.lines()` 对于处理行作为 *incoming* `Stream` 非常有用,但是如果你想在 `Stream` 中读,操作或写怎么办?这就需要稍微复杂的代码:
+
+```java
+// files/StreamInAndOut.java
+import java.io.*;
+import java.nio.file.*;
+import java.util.stream.*;
+
+public class StreamInAndOut {
+ public static void main(String[] args) {
+ try(
+ Stream input =
+ Files.lines(Paths.get("StreamInAndOut.java"));
+ PrintWriter output =
+ new PrintWriter("StreamInAndOut.txt")
+ ) {
+ input
+ .map(String::toUpperCase)
+ .forEachOrdered(output::println);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+因为我们在同一个块中执行所有操作,所以这两个文件都可以在相同的try-with-resources语句中打开。`PrintWriter` 是一个旧式的`java.io` 类,允许你“打印”到一个文件,所以它是这个应用程序的理想选择。如果你看一下 `StreamInAndOut.txt`,你会发现它确实是大写的。
## 本章小结
+虽然这是对文件和目录操作的相当全面的介绍,但是库中仍然有没被介绍的功能 - 一定要研究 `java.nio.file` 的Javadocs,尤其是`java.nio.file.Files` 的。
+Java 7和8对于处理文件和目录的库中做了大量改进。如果您刚刚开始使用Java,那么您很幸运。在过去,它非常令人不愉快,我确信Java 设计者以前对于文件操作不够重视才没做简化。对于初学者来说这是一件很棒的事,对于教学者来说也一样。我不明白为什么花了这么长时间来解决这个明显的问题,但不过它被解决了,我很高兴。使用文件现在很简单,甚至很有趣,这是以前你永远想不到的。
From 942aeaf5221d2738ed40d4acc34ea417854ad6d9 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Tue, 13 Aug 2019 10:00:22 +0800
Subject: [PATCH 022/449] =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 358234fd..51a798b7 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@
- [x] [第十四章 流式编程](docs/book/14-Streams.md)
- [x] [第十五章 异常](docs/book/15-Exceptions.md)
- [ ] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md)
-- [ ] [第十七章 文件](docs/book/17-Files.md)
+- [x] [第十七章 文件](docs/book/17-Files.md)
- [x] [第十八章 字符串](docs/book/18-Strings.md)
- [ ] [第十九章 类型信息](docs/book/19-Type-Information.md)
- [ ] [第二十章 泛型](docs/book/20-Generics.md)
From 98c8d443a4cdc37a249c3a9d466b1abe6d80fcba Mon Sep 17 00:00:00 2001
From: WangHengwei <35023376@qq.com>
Date: Tue, 13 Aug 2019 12:08:19 +0800
Subject: [PATCH 023/449] [hotfix] fix bugs
---
docs/book/12-Collections.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 24dfbf84..e227d1f3 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -771,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** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。
@@ -1057,7 +1057,7 @@ 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** ,以查看它是否包含某个键或某个值:
From 56438cdbe40c3e1de607e9a25efa9db14520917e Mon Sep 17 00:00:00 2001
From: WangHengwei
Date: Tue, 13 Aug 2019 21:57:50 +0800
Subject: [PATCH 024/449] [hotfix] fix typos
---
docs/book/12-Collections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 24dfbf84..809173d5 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -1538,7 +1538,7 @@ public class ArrayIsNotIterable {
### 适配器方法惯用法
-如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢? 例如,假设你希望可以选择是在正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现选择。
+如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,假设你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现选择。
一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。
在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:
From f2eb08f0eab1f62c36e49dac040c2dc00cfc9dc6 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Wed, 14 Aug 2019 10:01:05 +0800
Subject: [PATCH 025/449] Update 12-Collections.md
---
docs/book/12-Collections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index eb0c2188..407dcd96 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -1538,7 +1538,7 @@ public class ArrayIsNotIterable {
### 适配器方法惯用法
-如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,假设你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现选择。
+如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。
一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。
在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:
From 6744fd0e65f8c842335aefeb74f164ba1c19c6c4 Mon Sep 17 00:00:00 2001
From: WangHengwei
Date: Thu, 15 Aug 2019 13:52:39 +0800
Subject: [PATCH 026/449] [hotfix] fix typos
---
docs/book/13-Functional-Programming.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index 7b9473e2..3033ef85 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -27,7 +27,7 @@ OO(object oriented,面向对象)是抽象数据,FP(functional programm
更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。
-需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:“为并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 同时,函数式编程的参数能帮助程序员创建更快更健壮的代码 —— 部分仍然只是假设。虽然已有一些好的范例[^2],但我们还不能证明纯函数式语言就是解决编程问题的最佳方法。
+需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:“为并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 还请记住,程序员会利用函数式编程更高效地创建更健壮的代码这一观点存在部分假设性。虽然已有一些好的范例[^2],但我们还不能证明纯函数式语言就是解决编程问题的最佳方法。
FP 思想值得融入非 FP 语言,如 Python。Java 8 也从中吸收并支持了 FP。我们将在此章探讨。
@@ -106,7 +106,7 @@ Hello there Hello there
传统上,我们通过创建一个实现 **Strategy** 接口的类来实现此行为,比如在 **Soft**。
-- **[1]** 在 **Strategize** 中,**Soft** 作为默认策略,在初始化构造函数中赋值的。
+- **[1]** 在 **Strategize** 中,**Soft** 作为默认策略,在构造函数中赋值。
- **[2]** 一种略显简短且更自发的方法是创建一个**匿名内部类**。即使这样,仍有相当数量的冗余代码。你总是要仔细观察:“哦,原来这样,这里使用了匿名内部类。”
From 0646516ef4ce244de5b5c27c13f6eab0723d6ae9 Mon Sep 17 00:00:00 2001
From: LingCoder <34231795+LingCoder@users.noreply.github.com>
Date: Thu, 15 Aug 2019 17:00:36 +0800
Subject: [PATCH 027/449] Update 13-Functional-Programming.md
---
docs/book/13-Functional-Programming.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index 3033ef85..c3a612b8 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -27,7 +27,7 @@ OO(object oriented,面向对象)是抽象数据,FP(functional programm
更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。
-需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:“为并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 还请记住,程序员会利用函数式编程更高效地创建更健壮的代码这一观点存在部分假设性。虽然已有一些好的范例[^2],但我们还不能证明纯函数式语言就是解决编程问题的最佳方法。
+需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:为“并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例[^2],但还不足以证明纯函数式语言就是解决编程问题的最佳方法。
FP 思想值得融入非 FP 语言,如 Python。Java 8 也从中吸收并支持了 FP。我们将在此章探讨。
From 928d84715371d1488ffe1f93381a8223511afa7d Mon Sep 17 00:00:00 2001
From: WangHengwei
Date: Thu, 15 Aug 2019 17:07:51 +0800
Subject: [PATCH 028/449] [hotfix] fix typos
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
原文是“produce an exception”,我理解“产生”与“报出”有区别,产生的异常可以捕获处理,不知妥否。
---
docs/book/13-Functional-Programming.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index c3a612b8..4de9af7c 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -253,7 +253,7 @@ public class RecursiveFactorial {
3628800
```
-这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则无限递归并报异常。
+这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则将无限递归并产生异常。
我们可以将 `Fibonacci` 序列改为使用递归 Lambda 表达式来实现,这次使用实例变量:
From 9807b4b03629afa93060b835940ea2d55bb37ff5 Mon Sep 17 00:00:00 2001
From: cogitates
Date: Thu, 15 Aug 2019 17:17:35 +0800
Subject: [PATCH 029/449] =?UTF-8?q?Fix=20issue=20#117=20=E6=95=B0=E7=BB=84?=
=?UTF-8?q?=E9=83=A8=E5=88=86=20=20=E7=AC=AC=E4=B8=89=EF=BC=8C=E7=AC=AC?=
=?UTF-8?q?=E5=9B=9B=E5=B0=8F=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: cogitates
---
docs/book/21-Arrays.md | 260 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 260 insertions(+)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index 9c40c66f..caeb1164 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -306,10 +306,270 @@ a = d;
## 返回数组
+假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对C++/C这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。
+
+而在Java中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。
+
+下面,我们返回一个 **字符串** 数组:
+
+```Java
+// arrays/IceCreamFlavors.java
+// Returning arrays from methods
+import java.util.*;
+import static onjava.ArrayShow.*;
+
+public class IceCreamFlavors {
+ private static SplittableRandom rand =
+ new SplittableRandom(47);
+ static final String[] FLAVORS = {
+ "Chocolate", "Strawberry", "Vanilla Fudge Swirl",
+ "Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
+ "Praline Cream", "Mud Pie"
+ };
+ public static String[] flavorSet(int n) {
+ if(n > FLAVORS.length)
+ throw new IllegalArgumentException("Set too big");
+ String[] results = new String[n];
+ boolean[] picked = new boolean[FLAVORS.length];
+ for(int i = 0; i < n; i++) {
+ int t;
+ do
+ t = rand.nextInt(FLAVORS.length);
+ while(picked[t]);
+ results[i] = FLAVORS[t];
+ picked[t] = true;
+ }
+ return results;
+ }
+ public static void main(String[] args) {
+ for(int i = 0; i < 7; i++)
+ show(flavorSet(3));
+ }
+}
+/* Output:
+[Praline Cream, Mint Chip, Vanilla Fudge Swirl]
+[Strawberry, Vanilla Fudge Swirl, Mud Pie]
+[Chocolate, Strawberry, Vanilla Fudge Swirl]
+[Rum Raisin, Praline Cream, Chocolate]
+[Mint Chip, Rum Raisin, Mocha Almond Fudge]
+[Mocha Almond Fudge, Mud Pie, Vanilla Fudge Swirl]
+[Mocha Almond Fudge, Mud Pie, Mint Chip]
+*/
+```
+
+
+
+**flaverset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后选择从数组 **FLAVORS** 中随机选择flavors并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何的对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者在其他的什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。
+
+如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/generics) 中介绍的 **元组** 。
+
+注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串
+
+比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flvorSet()** 每一次都是按照随机顺序选择 flavors。
+
+直到书中的这个点,随机数通过 **java.util.Random** 类生成的,这个类从Java 1.0就有,甚至被更新以提供Java 8 流。现在我们可以介绍Java 8中的 **SplittableRandom** ,它不只是以线性操作工作(你最终会学到),还提供了一个高质量的随机数。我们将在这本书的后面部分使用 **SplittableRandom** 。
## 多维数组
+要创建多维的基元数组,你要用大括号来界定数组中的向量:
+
+```Java
+// arrays/MultidimensionalPrimitiveArray.java
+import java.util.*;
+
+public class MultidimensionalPrimitiveArray {
+ public static void main(String[] args) {
+ int[][] a = {
+ { 1, 2, 3, },
+ { 4, 5, 6, },
+ };
+ System.out.println(Arrays.deepToString(a));
+ }
+}
+/* Output:
+[[1, 2, 3], [4, 5, 6]]
+*/。
+```
+
+每个嵌套的大括号都代表了数组的一个维度。
+
+这个例子使用 **array.deepToString()** 方法,这将多维数组转换成 **String** 类型,就像在输出中显示的那样。
+
+你也可以使用 **new** 分配数组。 这是一个使用 **new** 表达式分配的三维数组:
+
+```Java
+// arrays/ThreeDWithNew.java
+import java.util.*;
+
+public class ThreeDWithNew {
+ public static void main(String[] args) {
+ // 3-D array with fixed length:
+ int[][][] a = new int[2][2][4];
+ System.out.println(Arrays.deepToString(a));
+ }
+}
+/* Output:
+[[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0,
+0]]]
+*/
+```
+
+倘若你不对基元数组进行显式的初始化,它的值会自动初始化。而对象数组将被初始化为 **null** 。
+
+组成矩阵的数组中每一个向量都可以是任意长度的(这叫做不规则数组):
+
+```Java
+// arrays/RaggedArray.java
+import java.util.*;
+
+public class RaggedArray {
+ static int val = 1;
+ public static void main(String[] args) {
+ SplittableRandom rand = new SplittableRandom(47);
+ // 3-D array with varied-length vectors:
+ int[][][] a = new int[rand.nextInt(7)][][];
+ for(int i = 0; i < a.length; i++) {
+ a[i] = new int[rand.nextInt(5)][];
+ for(int j = 0; j < a[i].length; j++) {
+ a[i][j] = new int[rand.nextInt(5)];
+ Arrays.setAll(a[i][j], n -> val++); // [1]
+ }
+ }
+ System.out.println(Arrays.deepToString(a));
+ }
+}
+/* Output:
+[[[1], []], [[2, 3, 4, 5], [6]], [[7, 8, 9], [10, 11,
+12], []]]
+*/
+```
+
+第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个在for循环中的 **new** 给数组填充了元素,第三个 **new** 为数组的最后一个索引填充元素。
+
+* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数接口 **IntunaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的lambda表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。
+
+非基元的对象数组也可以定义为不规则数组。这里,我们收集了许多使用大括号的 **new** 表达式:
+
+```Java
+// arrays/MultidimensionalObjectArrays.java
+import java.util.*;
+
+public class MultidimensionalObjectArrays {
+ public static void main(String[] args) {
+ BerylliumSphere[][] spheres = {
+ { new BerylliumSphere(), new BerylliumSphere() },
+ { new BerylliumSphere(), new BerylliumSphere(),
+ new BerylliumSphere(), new BerylliumSphere() },
+ { new BerylliumSphere(), new BerylliumSphere(),
+ new BerylliumSphere(), new BerylliumSphere(),
+ new BerylliumSphere(), new BerylliumSphere(),
+ new BerylliumSphere(), new BerylliumSphere() },
+ };
+ System.out.println(Arrays.deepToString(spheres));
+ }
+}
+/* Output:
+[[Sphere 0, Sphere 1], [Sphere 2, Sphere 3, Sphere 4,
+Sphere 5], [Sphere 6, Sphere 7, Sphere 8, Sphere 9,
+Sphere 10, Sphere 11, Sphere 12, Sphere 13]]
+*/
+```
+
+数组初始化时使用自动装箱技术:
+
+```Java
+// arrays/AutoboxingArrays.java
+import java.util.*;
+
+public class AutoboxingArrays {
+ public static void main(String[] args) {
+ Integer[][] a = { // Autoboxing:
+ { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
+ { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
+ { 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
+ { 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 },
+ };
+ System.out.println(Arrays.deepToString(a));
+ }
+}
+/* Output:
+[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [21, 22, 23, 24, 25,
+26, 27, 28, 29, 30], [51, 52, 53, 54, 55, 56, 57, 58,
+59, 60], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]]
+*/
+```
+
+以下是如何逐个构建非基元的对象数组:
+
+```Java
+// arrays/AssemblingMultidimensionalArrays.java
+// Creating multidimensional arrays
+import java.util.*;
+
+public class AssemblingMultidimensionalArrays {
+ public static void main(String[] args) {
+ Integer[][] a;
+ a = new Integer[3][];
+ for(int i = 0; i < a.length; i++) {
+ a[i] = new Integer[3];
+ for(int j = 0; j < a[i].length; j++)
+ a[i][j] = i * j; // Autoboxing
+ }
+ System.out.println(Arrays.deepToString(a));
+ }
+}
+/* Output:
+[[0, 0, 0], [0, 1, 2], [0, 2, 4]]
+*/
+```
+
+**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。
+
+**Arrays.deepToString()** 方法同时适用于基元数组和对象数组:
+
+```JAVA
+// arrays/MultiDimWrapperArray.java
+// (c)2017 MindView LLC: see Copyright.txt
+// We make no guarantees that this code is fit for any purpose.
+// Visit http://OnJava8.com for more book information.
+// Multidimensional arrays of "wrapper" objects
+import java.util.*;
+
+public class MultiDimWrapperArray {
+ public static void main(String[] args) {
+ Integer[][] a1 = { // Autoboxing
+ { 1, 2, 3, },
+ { 4, 5, 6, },
+ };
+ Double[][][] a2 = { // Autoboxing
+ { { 1.1, 2.2 }, { 3.3, 4.4 } },
+ { { 5.5, 6.6 }, { 7.7, 8.8 } },
+ { { 9.9, 1.2 }, { 2.3, 3.4 } },
+ };
+ String[][] a3 = {
+ { "The", "Quick", "Sly", "Fox" },
+ { "Jumped", "Over" },
+ { "The", "Lazy", "Brown", "Dog", "&", "friend" },
+ };
+ System.out.println(
+ "a1: " + Arrays.deepToString(a1));
+ System.out.println(
+ "a2: " + Arrays.deepToString(a2));
+ System.out.println(
+ "a3: " + Arrays.deepToString(a3));
+ }
+}
+/* Output:
+a1: [[1, 2, 3], [4, 5, 6]]
+a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7,
+8.8]], [[9.9, 1.2], [2.3, 3.4]]]
+a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The,
+Lazy, Brown, Dog, &, friend]]
+*/
+```
+
+同样的,在 **Integer** 和 **Double** 数组中,自动装箱为可为你创建包装器对象。
## 泛型数组
From b2c4b990b71b61be377f9dca25987d3ce388e02e Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Thu, 15 Aug 2019 22:31:36 +0800
Subject: [PATCH 030/449] =?UTF-8?q?revision[13]=20=E4=BD=9C=E4=B8=BA?=
=?UTF-8?q?=E9=97=AD=E5=8C=85=E7=9A=84=E5=86=85=E9=83=A8=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/13-Functional-Programming.md | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index c3a612b8..95d622d3 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -1048,7 +1048,7 @@ O
**闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。
-考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语变量捕获 )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。
+考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。
首先,下例函数中,方法返回访问对象字段和方法参数。代码示例:
@@ -1094,7 +1094,7 @@ public class SharedStorage {
每次调用 `getAsInt()` 都会增加 `i`,表明存储是共享的。
-如果 `i` 是 `makeFun()` 的本地怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍然编译:
+如果 `i` 是 `makeFun()` 的局部变量怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍可以编译:
```java
// functional/Closure2.java
@@ -1126,7 +1126,7 @@ public class Closure3 {
}
```
-`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者实 `final` 效果的。
+`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。
如果使用 `final` 修饰 `x`和 `i`,就不能再递增它们的值了。代码示例:
@@ -1165,7 +1165,7 @@ public class Closure5 {
}
```
-我们可以通过将 `final` 关键字应用于变量声明来实现**等同 final 效果**, 不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。
+**等同 final 效果**意味着可以在变量声明前加上 **final** 关键字而不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。
通过在闭包中使用 `final` 关键字提前修饰变量 `x` 和 `i` , 我们解决了 `Closure5.java` 中的问题。代码示例:
@@ -1206,7 +1206,7 @@ public class Closure7 {
}
```
-编译器非常智能,它能识别变量 `i` 的值正在被更改。 对于包装类型的处理可能比较特殊,所以让我们使用下面的 List 的例子。代码示例:
+编译器非常智能,它能识别变量 `i` 的值被更改过了。 对于包装类型的处理可能比较特殊,因此我们尝试下 **List**:
```java
// functional/Closure8.java
@@ -1244,7 +1244,7 @@ public class Closure8 {
[1, 96]
```
-可以看到,这次一切正常。我们改变了 List 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList` 他们不能互相干扰和共享。
+可以看到,这次一切正常。我们改变了 **List** 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList`, 它们之间互不干扰。
请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。
@@ -1271,6 +1271,7 @@ public class Closure9 {
让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是“在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。
+
### 作为闭包的内部类
我们可以复制我们的例子使用匿名内部类:
From eb6c5beac5d04cb918940e80507c6956d5885b8b Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sat, 17 Aug 2019 00:22:27 +0800
Subject: [PATCH 031/449] update
---
docs/book/19-Type-Information.md | 178 +++++++++++++++++++++++++++++++
1 file changed, 178 insertions(+)
diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md
index 3cdbfe22..fb838147 100644
--- a/docs/book/19-Type-Information.md
+++ b/docs/book/19-Type-Information.md
@@ -850,6 +850,184 @@ Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
`instanceof` 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 `Class` 对象作比较。在前面的例子中,你可能会觉得写出一大堆 `instanceof` 表达式很乏味,事实也是如此。但是,也没有办法让 `instanceof` 聪明起来,让它能够自动地创建一个 `Class` 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 `instanceof`,那就说明你的设计可能存在瑕疵。
+## 使用类字面量
+
+如果我们使用类字面量重新实现 `PetCreator` 类的话,其结果在很多方面都会更清晰:
+
+
+```java
+// typeinfo/pets/LiteralPetCreator.java
+// 使用类字面量
+// {java typeinfo.pets.LiteralPetCreator}
+package typeinfo.pets;
+import java.util.*;
+
+public class LiteralPetCreator extends PetCreator {
+ // try 代码块不再需要
+ @SuppressWarnings("unchecked")
+ public static
+ final List> ALL_TYPES =
+ Collections.unmodifiableList(Arrays.asList(
+ Pet.class, Dog.class, Cat.class, Rodent.class,
+ Mutt.class, Pug.class, EgyptianMau.class,
+ Manx.class, Cymric.class, Rat.class,
+ Mouse.class, Hamster.class));
+ // 用于随机创建的类型:
+ private static final
+ List> TYPES =
+ ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class),
+ ALL_TYPES.size());
+ @Override
+ public List> types() {
+ return TYPES;
+ }
+ public static void main(String[] args) {
+ System.out.println(TYPES);
+ }
+}
+/* 输出:
+[class typeinfo.pets.Mutt, class typeinfo.pets.Pug,
+class typeinfo.pets.EgyptianMau, class
+typeinfo.pets.Manx, class typeinfo.pets.Cymric, class
+typeinfo.pets.Rat, class typeinfo.pets.Mouse, class
+typeinfo.pets.Hamster]
+*/
+```
+
+在即将到来的 `PetCount3.java` 示例中,我们用所有 `Pet` 类型预先加载一个 `Map`(不仅仅是随机生成的),因此 `ALL_TYPES` 类型的列表是必要的。`types` 列表是 `ALL_TYPES` 类型(使用 `List.subList()` 创建)的一部分,它包含精确的宠物类型,因此用于随机生成 `Pet`。
+
+这次,`types` 的创建没有被 `try` 块包围,因为它是在编译时计算的,因此不会引发任何异常,不像 `Class.forName()`。
+
+我们现在在 `typeinfo.pets` 库中有两个 `PetCreator` 的实现。为了提供第二个作为默认实现,我们可以创建一个使用 `LiteralPetCreator` 的 *外观模式*:
+
+We now have two implementations of `PetCreator` in the `typeinfo.pets` library. To provide the second one as a default implementation, we can create a *Façade* that utilizes `LiteralPetCreator`:
+
+```java
+// typeinfo/pets/Pets.java
+// Facade to produce a default PetCreator
+package typeinfo.pets;
+import java.util.*;
+import java.util.stream.*;
+
+public class Pets {
+ public static final PetCreator CREATOR =
+ new LiteralPetCreator();
+
+ public static Pet get() {
+ return CREATOR.get();
+ }
+
+ public static Pet[] array(int size) {
+ Pet[] result = new Pet[size];
+ for(int i = 0; i < size; i++)
+ result[i] = CREATOR.get();
+ return result;
+ }
+
+ public static List list(int size) {
+ List result = new ArrayList<>();
+ Collections.addAll(result, array(size));
+ return result;
+ }
+
+ public static Stream stream() {
+ return Stream.generate(CREATOR);
+ }
+}
+```
+
+这还提供了对 `get()`、`array()` 和 `list()` 的间接调用,以及生成 `Stream` 的新方法。
+
+因为 `PetCount.countPets()` 采用了 `PetCreator` 参数,所以我们可以很容易地测试 `LiteralPetCreator`(通过上面的外观模式):
+
+```java
+// typeinfo/PetCount2.java
+import typeinfo.pets.*;
+
+public class PetCount2 {
+ public static void main(String[] args) {
+ PetCount.countPets(Pets.CREATOR);
+ }
+}
+/* 输出:
+Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
+EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse
+Pug Mouse Cymric
+{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,
+Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
+*/
+```
+
+输出与 `PetCount.java` 的输出相同。
+
+## 一个动态 `instanceof` 函数
+
+`Class.isInstance()` 方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 `instanceof` 语句都可以从 `PetCount.java` 中删除:
+
+```java
+// typeinfo/PetCount3.java
+// 使用 isInstance() 方法
+import java.util.*;
+import java.util.stream.*;
+import onjava.*;
+import typeinfo.pets.*;
+
+public class PetCount3 {
+ static class Counter extends
+ LinkedHashMap, Integer> {
+
+ Counter() {
+ super(LiteralPetCreator.ALL_TYPES.stream()
+ .map(lpc -> Pair.make(lpc, 0))
+ .collect(
+ Collectors.toMap(Pair::key, Pair::value)));
+ }
+
+ public void count(Pet pet) {
+ // Class.isInstance() 替换 instanceof:
+ entrySet().stream()
+ .filter(pair -> pair.getKey().isInstance(pet))
+ .forEach(pair ->
+ put(pair.getKey(), pair.getValue() + 1));
+ }
+
+ @Override
+ public String toString() {
+ String result = entrySet().stream()
+ .map(pair -> String.format("%s=%s",
+ pair.getKey().getSimpleName(),
+ pair.getValue()))
+ .collect(Collectors.joining(", "));
+ return "{" + result + "}";
+ }
+ }
+
+ public static void main(String[] args) {
+ Counter petCount = new Counter();
+ Pets.stream()
+ .limit(20)
+ .peek(petCount::count)
+ .forEach(p -> System.out.print(
+ p.getClass().getSimpleName() + " "));
+ System.out.println("\n" + petCount);
+ }
+}
+/* 输出:
+Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
+EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse
+Pug Mouse Cymric
+{Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5,
+EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}
+*/
+```
+
+为了计算所有不同类型的 `Pet`,`Counter Map` 预先加载了来自 `LiteralPetCreator.ALL_TYPES` 的类型。如果不预先加载 `Map`,将只计数随机生成的类型,而不是像 `Pet` 和 `Cat` 这样的基本类型。
+
+`isInstance()` 方法消除了对 `instanceof` 表达式的需要。此外,这意味着你可以通过更改 `LiteralPetCreator.types` 数组来添加新类型的 `Pet`;程序的其余部分不需要修改(就像使用 `instanceof` 表达式时那样)。
+
+`toString()` 方法被重载,以便更容易读取输出,该输出仍与打印 `Map` 时看到的典型输出匹配。
+
+### Counting Recursively
## 类型转换检测
From 7ec5073a3422e2b05a29d8667cec3bc47481b16c Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sat, 17 Aug 2019 01:22:33 +0800
Subject: [PATCH 032/449] update
---
docs/book/19-Type-Information.md | 179 ++++++++++++++++++++++---------
1 file changed, 128 insertions(+), 51 deletions(-)
diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md
index fb838147..ea6af1ad 100644
--- a/docs/book/19-Type-Information.md
+++ b/docs/book/19-Type-Information.md
@@ -5,22 +5,21 @@
> RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息
-RTTI把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。对RTTI的需要,揭示了面向对象设计中许多有趣(并且复杂)的特性,同时也带来了关于如何组织程序的基本问题。
+RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。对 RTTI 的需要,揭示了面向对象设计中许多有趣(并且复杂)的特性,同时也带来了关于如何组织程序的基本问题。
-本章将讨论Java是如何在运行时识别对象和类信息的。主要有两种方式:
+本章将讨论 Java 是如何在运行时识别对象和类信息的。主要有两种方式:
1. “传统的” RTTI:假定我们在编译时已经知道了所有的类型;
2. “反射”机制:允许我们在运行时发现和使用类的信息。
+## 为什么需要 RTTI
-## 为什么需要RTTI
-
-下面看一下我们已经很熟悉的一个例子,它使用了多态的类层次结构。基类`Shape`是泛化的类型,从它派生出了三个具体类: `Circle` 、`Square` 和 `Triangle` (见下图所示)。
+下面看一下我们已经很熟悉的一个例子,它使用了多态的类层次结构。基类 `Shape` 是泛化的类型,从它派生出了三个具体类: `Circle` 、`Square` 和 `Triangle`(见下图所示)。

-这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从`Shape`派生出`Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape`接口中动态绑定了`draw()`方法,这样做的目的就是让客户端程序员可以使用泛化的`Shape`引用来调用`draw()`。`draw()`方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以它可以使用`Shape`引用来调用,这就是多态。
+这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从 `Shape` 派生出 `Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape`接口中动态绑定了 `draw()` 方法,这样做的目的就是让客户端程序员可以使用泛化的 `Shape` 引用来调用 `draw()`。`draw()` 方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以它可以使用 `Shape` 引用来调用,这就是多态。
因此,我们通常会创建一个具体的对象(`Circle`、`Square` 或者 `Triangle`),把它向上转型成 `Shape` (忽略对象的具体类型),并且在后面的程序中使用 `Shape` 引用来调用在具体对象中被重载的方法(如 `draw()`)。
@@ -76,36 +75,35 @@ Triangle.draw()
另外在这个例子中,类型转换并不彻底:`Object` 被转型为 `Shape` ,而不是 `Circle`、`Square` 或者 `Triangle`。这是因为目前我们只能确保这个 `Stream` 保存的都是 `Shape`:
-- 编译期,`stream` 和 Java 泛型系统确保放入 `stream` 的都是 `Shape` 对象(`Shape` 子类的对象也可视为 `Shape` 的对象),否则编译器会报错;
+- 编译期,`stream` 和 Java 泛型系统确保放入 `stream` 的都是 `Shape` 对象(`Shape` 子类的对象也可视为 `Shape` 的对象),否则编译器会报错;
- 运行时,自动类型转换确保了从 `stream` 中取出的对象都是 `Shape` 类型。
接下来就是多态机制的事了,`Shape` 对象实际执行什么样的代码,是由引用所指向的具体对象(`Circle`、`Square` 或者 `Triangle`)决定的。这也符合我们编写代码的一般需求,通常,我们希望大部分代码尽可能少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(本例中即为 `Shape`)。这样,代码会更容易写,更易读和维护;设计也更容易实现,更易于理解和修改。所以多态是面向对象的基本目标。
-但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。
+但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而 Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。
-
## `Class` 对象
-要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有"常规"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用RTTI的其它方式。
+要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有"常规"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用 RTTI 的其它方式。
-类是程序的一部分,每个类都有一个 `Class` 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 `Class` 对象(更恰当的说,是被保存在一个同名的 `.class` 文件中)。为了生成这个类的对象,Java虚拟机(JVM)先会调用"类加载器"子系统把这个类加载到内存中。
+类是程序的一部分,每个类都有一个 `Class` 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 `Class` 对象(更恰当的说,是被保存在一个同名的 `.class` 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用"类加载器"子系统把这个类加载到内存中。
-类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是JVM实现的一部分。原生类加载器加载的是”可信类”(包括Java API类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持Web服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。
+类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是JVM实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。
-所有的类都是第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。
+所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。
> 其实构造器也是类的静态方法,虽然构造器前面并没有 `static` 关键字。所以,使用 `new` 操作符创建类的新对象,这个操作也算作对类的静态成员引用。
-因此,Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得Java具有一些静态加载语言(如C++)很难或者根本不可能实现的特性。
+因此,Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得 Java 具有一些静态加载语言(如 C++)很难或者根本不可能实现的特性。
-类加载器首先会检查这个类的 `Class` 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 `.class` 文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM会对其进行验证,确保它没有损坏,并且不包含不良的Java代码(这是Java安全防范的一种措施)。
+类加载器首先会检查这个类的 `Class` 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 `.class` 文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM 会对其进行验证,确保它没有损坏,并且不包含不良的 Java 代码(这是 Java 安全防范的一种措施)。
一旦某个类的 `Class` 对象被载入内存,它就可以用来创建这个类的所有对象。下面的示范程序可以证明这点:
```java
// typeinfo/SweetShop.java
-// Examination of the way the class loader works
+// 检查类加载器工作方式
class Cookie {
static { System.out.println("Loading Cookie"); }
}
@@ -157,7 +155,7 @@ After creating Cookie
Class.forName("Gum");
```
-所有 `Class` 对象都属于 `Class` 类,而且它跟其他普通对象一样,我们可以获取和操控它的引用(这也是类加载器的工作)。`forName()` 是 `Class` 类的一个静态方法,我们可以使用 `forName()` 根据目标类的类名(`String`)得到该类的 `Class` 对象。上面的代码忽略了 `forName()` 的返回值,因为那个调用是为了得到它产生的“副作用”。从结果可以看出,`forName()` 执行的副作用是如果`Gum`类没有被加载就加载它,而在加载的过程中,`Gum` 的 `static` 初始化块被执行了。
+所有 `Class` 对象都属于 `Class` 类,而且它跟其他普通对象一样,我们可以获取和操控它的引用(这也是类加载器的工作)。`forName()` 是 `Class` 类的一个静态方法,我们可以使用 `forName()` 根据目标类的类名(`String`)得到该类的 `Class` 对象。上面的代码忽略了 `forName()` 的返回值,因为那个调用是为了得到它产生的“副作用”。从结果可以看出,`forName()` 执行的副作用是如果 `Gum` 类没有被加载就加载它,而在加载的过程中,`Gum` 的 `static` 初始化块被执行了。
还需要注意的是,如果 `Class.forName()` 找不到要加载的类,它就会抛出异常 `ClassNotFoundException`。上面的例子中我们只是简单地报告了问题,但在更严密的程序里,就要考虑在异常处理程序中把问题解决掉(具体例子详见[设计模式](./25-Patterns)章节)。
@@ -165,7 +163,7 @@ Class.forName("Gum");
```java
// typeinfo/toys/ToyTest.java
-// Testing class Class
+// 测试 Class 类
// {java typeinfo.toys.ToyTest}
package typeinfo.toys;
@@ -174,8 +172,7 @@ interface Waterproof {}
interface Shoots {}
class Toy {
- // Comment out the following no-arg
- // constructor to see NoSuchMethodError
+ // 注释下面的无参数构造器会引起 NoSuchMethodError 错误
Toy() {}
Toy(int i) {}
}
@@ -194,6 +191,7 @@ public class ToyTest {
System.out.println(
"Canonical name : " + cc.getCanonicalName());
}
+
public static void main(String[] args) {
Class c = null;
try {
@@ -202,11 +200,14 @@ public class ToyTest {
System.out.println("Can't find FancyToy");
System.exit(1);
}
+
printInfo(c);
for(Class face : c.getInterfaces())
printInfo(face);
+
Class up = c.getSuperclass();
Object obj = null;
+
try {
// Requires no-arg constructor:
obj = up.newInstance();
@@ -217,6 +218,7 @@ public class ToyTest {
System.out.println("Cannot access");
System.exit(1);
}
+
printInfo(obj.getClass());
}
}
@@ -245,7 +247,7 @@ Simple name: Toy
Canonical name : typeinfo.toys.Toy
```
-`FancyToy` 继承自 `Toy` 并实现了 `HasBatteries`、`Waterproof` 和 `Shoots` 接口。在 主方法中,我们创建了一个 `Class` 引用,然后在 `try` 语句里边用 `forName()` 方法创建了一个 `FancyToy` 的类对象并赋值给该引用。需要注意的是,传递给 `forName()` 的字符串必须使用类的全限定名(包含包名)。
+`FancyToy` 继承自 `Toy` 并实现了 `HasBatteries`、`Waterproof` 和 `Shoots` 接口。在 `main` 方法中,我们创建了一个 `Class` 引用,然后在 `try` 语句里边用 `forName()` 方法创建了一个 `FancyToy` 的类对象并赋值给该引用。需要注意的是,传递给 `forName()` 的字符串必须使用类的全限定名(包含包名)。
`printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。
@@ -253,7 +255,7 @@ Canonical name : typeinfo.toys.Toy
另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。
-`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造书。在本章稍后部分,你将会看到如何通过Java的反射API,用任意的构造器来动态的创建类的对象。
+`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造书。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。
### 类字面常量
@@ -308,11 +310,12 @@ Java还提供了另一种方法来生成类对象的引用:**类字面常量**
-我的建议是使用 `.class` 的形式, 以保持与普通类的一致性。
+
+我的建议是使用 `.class` 的形式,以保持与普通类的一致性。
注意,有一点很有趣:当使用 `.class` 来创建对 `Class` 对象的引用时,不会自动地初始化该`Class` 对象。为了使用类而做的准备工作实际包含三个步骤:
-1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。
+1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。
2. **链接**。在链接阶段将验证类中的字节码,为 `static` 域分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。
@@ -392,7 +395,7 @@ After creating Initable3 ref
-但是,Java设计者看准机会,将它的类型变得更具体了一些。Java引入泛型语法之后,我们可以使用泛型对 `Class` 引用所指向的 `Class` 对象的类型进行限定。在下面的实例中,两种语法都是正确的:
+但是,Java 设计者看准机会,将它的类型变得更具体了一些。Java 引入泛型语法之后,我们可以使用泛型对 `Class` 引用所指向的 `Class` 对象的类型进行限定。在下面的实例中,两种语法都是正确的:
```java
// typeinfo/GenericClassReferences.java
@@ -401,9 +404,9 @@ public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class genericIntClass = int.class;
- genericIntClass = Integer.class; // Same thing
+ genericIntClass = Integer.class; // 同一个东西
intClass = double.class;
- // genericIntClass = double.class; // Illegal
+ // genericIntClass = double.class; // 非法
}
}
```
@@ -418,7 +421,7 @@ Class geenericNumberClass = int.class;
这看起来似乎是起作用的,因为 `Integer` 继承自 `Number`。但事实却是不行,因为 `Integer` 的 `Class` 对象并不是 `Number`的 `Class` 对象的子类(这看起来可能有点诡异,我们将在[泛型](./20-Generics)这一章详细讨论)。
-为了在使用 `Class` 引用时放松限制,我们使用了通配符,它是Java泛型中的一部分。通配符就是 `?`,表示“任何事物”。因此,我们可以在上例的普通 `Class` 引用中添加通配符,并产生相同的结果:
+为了在使用 `Class` 引用时放松限制,我们使用了通配符,它是 Java 泛型中的一部分。通配符就是 `?`,表示“任何事物”。因此,我们可以在上例的普通 `Class` 引用中添加通配符,并产生相同的结果:
```java
// typeinfo/WildcardClassReferences.java
@@ -503,7 +506,7 @@ public class DynamicSupplier implements Supplier {
```java
// typeinfo/toys/GenericToyTest.java
-// Testing class Class
+// 测试 Class 类
// {java typeinfo.toys.GenericToyTest}
package typeinfo.toys;
@@ -527,7 +530,7 @@ public class GenericToyTest {
### `cast()` 方法
-Java中还有用于 `Class` 引用的转型语法,即 `cast()` 方法:
+Java 中还有用于 `Class` 引用的转型语法,即 `cast()` 方法:
```java
// typeinfo/ClassCasts.java
@@ -540,26 +543,26 @@ public class ClassCasts {
Building b = new House();
Class houseType = House.class;
House h = houseType.cast(b);
- h = (House)b; // ... or just do this.
+ h = (House)b; // ... 或者这样做.
}
}
```
-`cast()` 方法接受参数对象,并将其转型为 `Class` 引用的类型。但是,如果观察上面的代码,你就会发现,与实现了相同功能的主方法中最后一行相比,这种转型好像做了很多额外的工作。
+`cast()` 方法接受参数对象,并将其类型转换为 `Class` 引用的类型。但是,如果观察上面的代码,你就会发现,与实现了相同功能的 `main` 方法中最后一行相比,这种转型好像做了很多额外的工作。
-`cast()` 在无法使用普通转型的情况下会显得非常有用,在你编写泛型代码(你将在[泛型](./20-Generics)这一章学习到)时,如果你保存了 `Class` 引用,并希望以后通过这个引用来执行转型,你就需要用到 `cast()`。但事实却是这种情况并不常见,我发现整个Java类库中,只有一处使用了 `cast()`(在 `com.sun.mirror.util.DeclarationFilter` 中)。
+`cast()` 在无法使用普通类型转换的情况下会显得非常有用,在你编写泛型代码(你将在[泛型](./20-Generics)这一章学习到)时,如果你保存了 `Class` 引用,并希望以后通过这个引用来执行转型,你就需要用到 `cast()`。但事实却是这种情况非常少见,我发现整个 Java 类库中,只有一处使用了 `cast()`(在 `com.sun.mirror.util.DeclarationFilter` 中)。
-Java类库中另一个没有任何用处的特性就是 `Class.asSubclass()`,该方法允许你将一个 `Class` 对象转型为更加具体的类型。
+Java 类库中另一个没有任何用处的特性就是 `Class.asSubclass()`,该方法允许你将一个 `Class` 对象转型为更加具体的类型。
-## 类型转换前先做检查
+## 类型转换检测
直到现在,我们已知的RTTI类型包括:
-1. 传统的类型转换,如 “`(Shape)`”,由RTTI确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 `ClassCastException` 异常。
+1. 传统的类型转换,如 “`(Shape)`”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 `ClassCastException` 异常。
2. 代表对象类型的 `Class` 对象. 通过查询 `Class` 对象可以获取运行时所需的信息.
-在C++中,经典的类型转换 “`(Shape)`” 并不使用 RTTI. 它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显示的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式的类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类型的子类的类型上)。
+在 C++ 中,经典的类型转换 “`(Shape)`” 并不使用 RTTI. 它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显示的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式的类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类型的子类的类型上)。
RTTI 在 Java 中还有第三种形式,那就是关键字 `instanceof`。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:
@@ -568,7 +571,7 @@ if(x instanceof Dog)
((Dog)x).bark();
```
-在将 `x` 转型为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。
+在将 `x` 的类型转换为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。
一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 `instanceof` 来计数所有对象。举个例子,假如你有一个类的继承体系,描述了 `Pet`(以及它们的主人,在后面一个例子中会用到这个特性)。在这个继承体系中的每个 `Individual` 都有一个 `id` 和一个可选的名字。尽管下面的类都继承自 `Individual`,但是 `Individual` 类复杂性较高,因此其代码将放在[附录:容器](./Appendix-Collection-Topics)中进行解释说明。正如你所看到的,此处并不需要去了解 `Individual` 的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 `Individual` 都有一个 `id()` 方法,如果你没有为 `Individual` 提供名字,`toString()` 方法只产生类型名。
@@ -746,7 +749,8 @@ import java.util.*;
public class ForNameCreator extends PetCreator {
private static List> types =
new ArrayList<>();
- // Types you want randomly created:
+
+ // 需要随机生成的类型名:
private static String[] typeNames = {
"typeinfo.pets.Mutt",
"typeinfo.pets.Pug",
@@ -757,6 +761,7 @@ public class ForNameCreator extends PetCreator {
"typeinfo.pets.Mouse",
"typeinfo.pets.Hamster"
};
+
@SuppressWarnings("unchecked")
private static void loader() {
try {
@@ -767,6 +772,7 @@ public class ForNameCreator extends PetCreator {
throw new RuntimeException(e);
}
}
+
static { loader(); }
@Override
public List> types() {
@@ -775,15 +781,15 @@ public class ForNameCreator extends PetCreator {
}
```
-`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException`,因为你传入的是一个 `String`,它不能再编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。
+`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException` 异常,因为你传入的是一个 `String` 类型的参数,它不能再编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。
-为了使得 `List` 装入的是具体的 `Class` 对象,转型是必须的,它会产生一个编译时警告。`loader()` 方法是分开编写的,然后它被放入到一个静态代码块里,因为 `@SuppressWarning` 注解不能够直接放置在静态代码块之上。
+为了使得 `List` 装入的是具体的 `Class` 对象,类型转换是必须的,它会产生一个编译时警告。`loader()` 方法是分开编写的,然后它被放入到一个静态代码块里,因为 `@SuppressWarning` 注解不能够直接放置在静态代码块之上。
为了对 `Pet` 进行计数,我们需要一个能跟踪不同类型的 `Pet` 的工具。`Map` 的是这个需求的首选,我们将 `Pet` 类型名作为键,将保存 `Pet` 数量的 `Integer` 作为值。通过这种方式,你就看可以询问:“有多少个 `Hamster` 对象?”我们可以使用 `instanceof` 来对 `Pet` 进行计数:
```java
// typeinfo/PetCount.java
-// Using instanceof
+// 使用 instanceof
import typeinfo.pets.*;
import java.util.*;
@@ -850,11 +856,10 @@ Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
`instanceof` 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 `Class` 对象作比较。在前面的例子中,你可能会觉得写出一大堆 `instanceof` 表达式很乏味,事实也是如此。但是,也没有办法让 `instanceof` 聪明起来,让它能够自动地创建一个 `Class` 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 `instanceof`,那就说明你的设计可能存在瑕疵。
-## 使用类字面量
+### 使用类字面量
如果我们使用类字面量重新实现 `PetCreator` 类的话,其结果在很多方面都会更清晰:
-
```java
// typeinfo/pets/LiteralPetCreator.java
// 使用类字面量
@@ -900,8 +905,6 @@ typeinfo.pets.Hamster]
我们现在在 `typeinfo.pets` 库中有两个 `PetCreator` 的实现。为了提供第二个作为默认实现,我们可以创建一个使用 `LiteralPetCreator` 的 *外观模式*:
-We now have two implementations of `PetCreator` in the `typeinfo.pets` library. To provide the second one as a default implementation, we can create a *Façade* that utilizes `LiteralPetCreator`:
-
```java
// typeinfo/pets/Pets.java
// Facade to produce a default PetCreator
@@ -960,7 +963,7 @@ Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
输出与 `PetCount.java` 的输出相同。
-## 一个动态 `instanceof` 函数
+### 一个动态 `instanceof` 函数
`Class.isInstance()` 方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 `instanceof` 语句都可以从 `PetCount.java` 中删除:
@@ -1027,12 +1030,86 @@ EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}
`toString()` 方法被重载,以便更容易读取输出,该输出仍与打印 `Map` 时看到的典型输出匹配。
-### Counting Recursively
+### 递归计数
-## 类型转换检测
+`PetCount3.Counter` 中的 `Map` 预先加载了所有不同的 `Pet` 类。我们可以使用 `Class.isAssignableFrom()` 而不是预加载地图,并创建一个不限于计数 `Pet` 的通用工具:
-
+```java
+// onjava/TypeCounter.java
+// 计算类型家族的实例数
+package onjava;
+import java.util.*;
+import java.util.stream.*;
+
+public class
+TypeCounter extends HashMap, Integer> {
+ private Class> baseType;
+
+ public TypeCounter(Class> baseType) {
+ this.baseType = baseType;
+ }
+
+ public void count(Object obj) {
+ Class> type = obj.getClass();
+ if(!baseType.isAssignableFrom(type))
+ throw new RuntimeException(
+ obj + " incorrect type: " + type +
+ ", should be type or subtype of " + baseType);
+ countClass(type);
+ }
+
+ private void countClass(Class> type) {
+ Integer quantity = get(type);
+ put(type, quantity == null ? 1 : quantity + 1);
+ Class> superClass = type.getSuperclass();
+ if(superClass != null &&
+ baseType.isAssignableFrom(superClass))
+ countClass(superClass);
+ }
+ @Override
+ public String toString() {
+ String result = entrySet().stream()
+ .map(pair -> String.format("%s=%s",
+ pair.getKey().getSimpleName(),
+ pair.getValue()))
+ .collect(Collectors.joining(", "));
+ return "{" + result + "}";
+ }
+}
+```
+
+`count()` 方法获取其参数的 `Class`,并使用 `isAssignableFrom()` 进行运行时检查,以验证传递的对象实际上属于感兴趣的层次结构。`countClass()` 首先计算类的确切类型。然后,如果 `baseType` 可以从超类赋值,则在超类上递归调用 `countClass()`。
+
+```java
+// typeinfo/PetCount4.java
+import typeinfo.pets.*;
+import onjava.*;
+
+public class PetCount4 {
+ public static void main(String[] args) {
+ TypeCounter counter = new TypeCounter(Pet.class);
+ Pets.stream()
+ .limit(20)
+ .peek(counter::count)
+ .forEach(p -> System.out.print(
+ p.getClass().getSimpleName() + " "));
+ System.out.println("\n" + counter);
+ }
+}
+/* 输出:
+Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
+EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse
+Pug Mouse Cymric
+{Dog=6, Manx=7, Cat=9, Rodent=5, Hamster=1, Rat=2,
+Pug=3, Mutt=3, Cymric=5, EgyptianMau=2, Pet=20,
+Mouse=2}
+*/
+```
+
+输出表明两个基类型以及精确类型都被计数了。
+
+
## 注册工厂
@@ -1041,7 +1118,7 @@ EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}
-## 反射运行时类信息
+## 内省:反射运行时类信息
From b96f893cb57fe91edaae8636868009c9c0e7f2b5 Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sat, 17 Aug 2019 19:35:10 +0800
Subject: [PATCH 033/449] update
---
docs/book/19-Type-Information.md | 283 ++++++++++++++++++++++++++++++-
1 file changed, 282 insertions(+), 1 deletion(-)
diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md
index ea6af1ad..43a27d5c 100644
--- a/docs/book/19-Type-Information.md
+++ b/docs/book/19-Type-Information.md
@@ -1112,14 +1112,295 @@ Mouse=2}
## 注册工厂
+从 `Pet` 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 `Pet` 时,必须记住将其添加到 `LiteralPetCreator.java` 中的条目中。在一个定期添加更多类的系统中,这可能会成为问题。
+
+你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。
+
+基本上,你必须自己手工创建列表(除非你编写了一个工具来搜索和分析源代码,然后创建和编译列表)。所以你能做的最好的事情就是把列表集中放在一个明显的地方。层次结构的基类可能是最好的地方。
+
+我们在这里所做的另一个更改是使用*工厂方法*设计模式将对象的创建推迟到类本身。工厂方法可以以多态方式调用,并为你创建适当类型的对象。事实证明,`java.util.function.Supplier` 用 `T get()` 描述了原型工厂方法。协变返回类型允许 `get()` 为 `Supplier` 的每个子类实现返回不同的类型。
+
+在本例中,基类 `Part` 包含一个工厂对象的静态列表,列表成员类型为 `Supplier`。对于应该由 `get()` 方法生成的类型的工厂,通过将它们添加到 `prototypes` 列表向基类“注册”。奇怪的是,这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的*原型*:
+
+```java
+// typeinfo/RegisteredFactories.java
+// 注册工厂到基础类
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+class Part implements Supplier {
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ static List> prototypes =
+ Arrays.asList(
+ new FuelFilter(),
+ new AirFilter(),
+ new CabinAirFilter(),
+ new OilFilter(),
+ new FanBelt(),
+ new PowerSteeringBelt(),
+ new GeneratorBelt()
+ );
+
+ private static Random rand = new Random(47);
+ public Part get() {
+ int n = rand.nextInt(prototypes.size());
+ return prototypes.get(n).get();
+ }
+}
+
+class Filter extends Part {}
+
+class FuelFilter extends Filter {
+ @Override
+ public FuelFilter get() { return new FuelFilter(); }
+}
+
+class AirFilter extends Filter {
+ @Override
+ public AirFilter get() { return new AirFilter(); }
+}
+
+class CabinAirFilter extends Filter {
+ @Override
+ public CabinAirFilter get() {
+ return new CabinAirFilter();
+ }
+}
+
+class OilFilter extends Filter {
+ @Override
+ public OilFilter get() { return new OilFilter(); }
+}
+
+class Belt extends Part {}
+
+class FanBelt extends Belt {
+ @Override
+ public FanBelt get() { return new FanBelt(); }
+}
+
+class GeneratorBelt extends Belt {
+ @Override
+ public GeneratorBelt get() {
+ return new GeneratorBelt();
+ }
+}
+
+class PowerSteeringBelt extends Belt {
+ @Override
+ public PowerSteeringBelt get() {
+ return new PowerSteeringBelt();
+ }
+}
+
+public class RegisteredFactories {
+ public static void main(String[] args) {
+ Stream.generate(new Part())
+ .limit(10)
+ .forEach(System.out::println);
+ }
+}
+
+/* 输出:
+GeneratorBelt
+CabinAirFilter
+GeneratorBelt
+AirFilter
+PowerSteeringBelt
+CabinAirFilter
+FuelFilter
+PowerSteeringBelt
+PowerSteeringBelt
+FuelFilter
+*/
+```
+
+并非层次结构中的所有类都应实例化;这里的 `Filter` 和 `Belt` 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 `Part` 基类的行为)。
+
+因为 `Part implements Supplier`,`Part` 通过其 `get()` 方法供应其他 `Part`。如果为基类 `Part` 调用 `get()`(或者如果 `generate()` 调用 `get()`),它将创建随机特定的 `Part` 子类型,每个子类型最终都从 `Part` 继承,并重写相应的 `get()` 以生成它们中的一个。
## 类的等价比较
+When you are querying for type information, there's an important difference between either form of `instanceof` (that is, `instanceof` or `isInstance()`, which produce equivalent results) and the direct comparison of the `Class` objects. Here's an example that demonstrates the difference:
+
+```java
+// typeinfo/FamilyVsExactType.java
+// instanceof 与 class 的差别
+// {java typeinfo.FamilyVsExactType}
+package typeinfo;
+
+class Base {}
+class Derived extends Base {}
+
+public class FamilyVsExactType {
+ static void test(Object x) {
+ System.out.println(
+ "Testing x of type " + x.getClass());
+ System.out.println(
+ "x instanceof Base " + (x instanceof Base));
+ System.out.println(
+ "x instanceof Derived " + (x instanceof Derived));
+ System.out.println(
+ "Base.isInstance(x) " + Base.class.isInstance(x));
+ System.out.println(
+ "Derived.isInstance(x) " +
+ Derived.class.isInstance(x));
+ System.out.println(
+ "x.getClass() == Base.class " +
+ (x.getClass() == Base.class));
+ System.out.println(
+ "x.getClass() == Derived.class " +
+ (x.getClass() == Derived.class));
+ System.out.println(
+ "x.getClass().equals(Base.class)) "+
+ (x.getClass().equals(Base.class)));
+ System.out.println(
+ "x.getClass().equals(Derived.class)) " +
+ (x.getClass().equals(Derived.class)));
+ }
+ public static void main(String[] args) {
+ test(new Base());
+ test(new Derived());
+ }
+}
+/* 输出:
+Testing x of type class typeinfo.Base
+x instanceof Base true
+x instanceof Derived false
+Base.isInstance(x) true
+Derived.isInstance(x) false
+x.getClass() == Base.class true
+x.getClass() == Derived.class false
+x.getClass().equals(Base.class)) true
+x.getClass().equals(Derived.class)) false
+Testing x of type class typeinfo.Derived
+x instanceof Base true
+x instanceof Derived true
+Base.isInstance(x) true
+Derived.isInstance(x) true
+x.getClass() == Base.class false
+x.getClass() == Derived.class true
+x.getClass().equals(Base.class)) false
+x.getClass().equals(Derived.class)) true
+*/
+```
+
+`test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果与 `equals()` 和 `==` 完全相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。另一方面,如果使用 `==` 比较实际的 `Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。
+
+## 反射:运行时类信息
+
+如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。
+
+起初,这看起来并没有那么大的限制,但是假设你被赋予了一个对不在程序空间中的对象的引用。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类?
+
+在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在程序时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆盖这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。
+
+在运行时发现类信息的另一个令人信服的动机是提供跨网络在远程平台上创建和执行对象的能力。这称为*远程方法调用*(RMI),它使 Java 程序的对象分布在许多机器上。这种分布有多种原因。如果你想加速一个计算密集型的任务,你可以把它分解成小块放到空闲的机器上。或者你可以将处理特定类型任务的代码(例如,多层次客户机/服务器体系结构中的“业务规则”)放在特定的机器上,这样机器就成为描述这些操作的公共存储库,并且可以很容易地更改它以影响系统中的每个人。分布式计算还支持专门的硬件,这些硬件可能擅长于某个特定的任务——例如矩阵转换——但对于通用编程来说不合适或过于昂贵。
+
+类 `Class` 支持*反射*的概念,以及 `java.lang.reflect` 库,其中包含类 `Field`、`Method` 和 `Constructor`(每一个都实现了 `Member` 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 `Constructor` 创建新对象,`get()` 和 `set()` 方法读取和修改与 `Field` 对象关联的字段,`invoke()` 方法调用与 `Method` 对象关联的方法。此外,还可以调用便利方法 `getFields()`、`getMethods()`、`getConstructors()` 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 `Class` 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。
+
+重要的是要意识到反射没有什么魔力。当你使用反射与未知类型的对象交互时,JVM 将查看该对象,并看到它属于特定的类(就像普通的 RTTI)。在对其执行任何操作之前,必须加载 `Class` 对象。因此,该特定类型的 `.class` 文件必须在本地计算机上或通过网络对 JVM 仍然可用。因此,RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 `.class` 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,`.class` 文件在编译时不可用;它由运行时环境打开并检查。
+
+### 类方法提取器
+
+通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](#ch040.xhtml#appendix-object-serialization))。但是,有时动态提取有关类的信息很有用。
+
+考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时。
+
+```java
+// typeinfo/ShowMethods.java
+// 使用反射展示一个类的所有方法,甚至包括定义在基类中方法
+// {java ShowMethods ShowMethods}
+import java.lang.reflect.*;
+import java.util.regex.*;
+
+public class ShowMethods {
+ private static String usage =
+ "usage:\n" +
+ "ShowMethods qualified.class.name\n" +
+ "To show all methods in class or:\n" +
+ "ShowMethods qualified.class.name word\n" +
+ "To search for methods involving 'word'";
+
+ private static Pattern p = Pattern.compile("\\w+\\.");
+ public static void main(String[] args) {
+
+ if(args.length < 1) {
+ System.out.println(usage);
+ System.exit(0);
+ }
+ int lines = 0;
+ try {
+ Class> c = Class.forName(args[0]);
+ Method[] methods = c.getMethods();
+ Constructor[] ctors = c.getConstructors();
+ if(args.length == 1) {
+ for(Method method : methods)
+ System.out.println(
+ p.matcher(
+ method.toString()).replaceAll(""));
+ for(Constructor ctor : ctors)
+ System.out.println(
+ p.matcher(ctor.toString()).replaceAll(""));
+ lines = methods.length + ctors.length;
+ } else {
+ for(Method method : methods)
+ if(method.toString().contains(args[1])) {
+ System.out.println(p.matcher(
+ method.toString()).replaceAll(""));
+ lines++;
+ }
+ for(Constructor ctor : ctors)
+ if(ctor.toString().contains(args[1])) {
+ System.out.println(p.matcher(
+ ctor.toString()).replaceAll(""));
+ lines++;
+ }
+ }
+ } catch(ClassNotFoundException e) {
+ System.out.println("No such class: " + e);
+ }
+ }
+}
+/* 输出:
+public static void main(String[])
+public final void wait() throws InterruptedException
+public final void wait(long,int) throws
+InterruptedException
+public final native void wait(long) throws
+InterruptedException
+public boolean equals(Object)
+public String toString()
+public native int hashCode()
+public final native Class getClass()
+public final native void notify()
+public final native void notifyAll()
+public ShowMethods()
+*/
+```
+
+`Class` 方法 `getmethods()` 和'getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。
+
+编译时无法知道 `Class.forName()` 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用(本书后面有这样的例子)。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。
+
+上面的输出来自命令行:
+
+```java
+java ShowMethods ShowMethods
+```
+
+输出包含一个 `public` 无参数构造函数,即使未定义构造函数。你看到的构造函数是由编译器自动合成的。如果将 `ShowMethods` 设置为非 `public` 类(即只有包级访问权),则合成的无参数构造函数将不再显示在输出中。自动为合成的无参数构造函数授予与类相同的访问权。
-## 内省:反射运行时类信息
+尝试运行 `java ShowMethods java.lang.String`,并附加一个 `char`、`int`、`String` 等参数。
+编程时,当你不记得某个类是否有特定的方法,并且不想在 JDK 文档中搜索索引或类层次结构时,或者如果你不知道该类是否可以对 `Color` 对象执行任何操作时,该工具能节省不少时间。
## 动态代理
From 3592ced80178b47eb5e23c936a7f8429194cf39d Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sat, 17 Aug 2019 19:56:42 +0800
Subject: [PATCH 034/449] update format
---
docs/book/00-Introduction.md | 21 ++++----------
docs/book/00-On-Java-8.md | 2 +-
docs/book/00-Preface.md | 31 ++++----------------
docs/book/01-What-is-an-Object.md | 48 +++++++++++--------------------
4 files changed, 27 insertions(+), 75 deletions(-)
diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md
index d34623bb..24646a9d 100644
--- a/docs/book/00-Introduction.md
+++ b/docs/book/00-Introduction.md
@@ -23,9 +23,6 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
通信革命使我们相互沟通更加便利。无论是一对一沟通,还是团队里的互相沟通,甚至是地球上不同地区的沟通。据说下一次革命需要的是一种全球性的思维,这种思维源于足量的人以及足量相互连接。不我不知道 Java 是否能成为这场革命的工具之一,但至少这种可能性让我觉得:我现在正在做的传道授业的事情是有意义的!
-
-
-
## 前提条件
阅读本书需要读者对编程有基本的了解:
@@ -41,17 +38,14 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面对对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。
-
-## JDK文档
+## JDK 文档
甲骨文公司已经提供了免费的标准 JDK 文档。除非有必要,否则本书中将不再赘述 API 相关的使用细节。使用浏览器来即时搜索最新最全的 JDK 文档好过翻阅本书来查找。只有在需要补充特定的示例时,我才会提供有关的额外描述。
-
-## C编程思想
+## C 编程思想
*Thinking in C* 已经可以在 www.OnJava8.com 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。
-
## 源码下载
本书中所有源代码的示例都在版权保护的前提下通过 GitHub 免费提供。你可以将这些代码用于教育。任何人不得在未经正确引用代码来源的情况下随意重新发布此代码示例。在每个代码文件中,你都可以找到以下版权声明文件作为参考:
@@ -62,7 +56,7 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
1. 允许编译源代码并将编译代码仅以可执行格式包含在个人和商业软件程序中。
-2. 允许在课堂情况下使用源代码而不修改源代码,包括在演示材料中,前提是“On Java 8”一书被引用为原点。
+2. 允许在课堂情况下使用源代码而不修改源代码,包括在演示材料中,前提是 “On Java 8” 一书被引用为原点。
3. 可以通过以下方式获得将源代码合并到印刷媒体中的许可:MindView LLC,PO Box 969,Crested Butte,CO 81224 MindViewInc@gmail.com
@@ -70,29 +64,24 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
5. 在任何情况下,MINDVIEW LLC 或其出版商均不对任何一方根据任何法律理论对直接,间接,特殊,偶发或后果性损害承担任何责任,包括利润损失,业务中断,商业信息丢失或任何其他保险公司。由于 MINDVIEW LLC 或其出版商已被告知此类损害的可能性,因此使用本源代码及其文档或因无法使用任何结果程序而导致的个人受伤或者个人受伤。MINDVIEW LLC 特别声明不提供任何担保,包括但不限于对适销性和特定用途适用性的暗示担保。此处提供的源代码和文档基于“原样”基础,没有MINDVIEW LLC的任何随附服务,MINDVIEW LLC 没有义务提供维护,支持,更新,增强或修改。
-
**请注意**,MindView LLC 仅提供以下唯一网址发布更新书中的代码示例,https://github.com/BruceEckel/OnJava8-examples 。你可在上述条款范围内将示例免费使用于项目和课堂中。
如果你在源代码中发现错误,请在下面的网址提交更正:https://github.com/BruceEckel/OnJava8-examples/issues
-
## 编码样式
-本书中代码标识符(关键字,方法,变量和类名)以粗体,固定宽度代码字体显示。像“*class”这种在代码中高频率出现的关键字可能让你觉得粗体有点乏味。(译者注:由于中英排版差异,中文翻译过程并未完全参照原作者的说明。具体排版格式请参考[此处](https://github.com/ruanyf/document-style-guide))其他显示为正常字体。本书文本格式尽可能遵循 Oracle 常见样式,并保证在大多数 Java 开发环境中被支持。书中我使用了自己喜欢的字体风格。Java 是一种自由的编程语言,你也可以使用 IDE(集成开发环境)工具(如 IntelliJ IDEA,Eclipse 或 NetBeans)将格式更改为适合你的格式。
-
-本书代码文件使用自动化工具进行测试,并在最新版本的 Java 编译通过(除了那些特别标记的错误之外)。本书重点介绍并使用 Java 8 进行测试。如果你必须了解更早的语言版本,可以在 [www.OnJava8.com](http://www.OnJava8.com) 免费下载 《Thinking in Java》。
+本书中代码标识符(关键字,方法,变量和类名)以粗体,固定宽度代码字体显示。像 “*class” 这种在代码中高频率出现的关键字可能让你觉得粗体有点乏味。(译者注:由于中英排版差异,中文翻译过程并未完全参照原作者的说明。具体排版格式请参考[此处](https://github.com/ruanyf/document-style-guide))其他显示为正常字体。本书文本格式尽可能遵循 Oracle 常见样式,并保证在大多数 Java 开发环境中被支持。书中我使用了自己喜欢的字体风格。Java 是一种自由的编程语言,你也可以使用 IDE(集成开发环境)工具(如 IntelliJ IDEA,Eclipse 或 NetBeans)将格式更改为适合你的格式。
+本书代码文件使用自动化工具进行测试,并在最新版本的 Java 编译通过(除了那些特别标记的错误之外)。本书重点介绍并使用 Java 8 进行测试。如果你必须了解更早的语言版本,可以在 [www.OnJava8.com](http://www.OnJava8.com) 免费下载 《Thinking in Java》。
## BUG提交
本书经过多重校订,但还是难免有所遗漏被新读者发现。如果你在正文或示例中发现任何错误的内容,请在[此处](https://github.com/BruceEckel/OnJava8-examples/issues)提交错误以及建议更正,作者感激不尽。
-
## 邮箱订阅
你可以在 [www.OnJava8.com上](http://www.OnJava8.com) 订阅邮件。邮件不含广告并尽量提供干货。
-
## Java图形界面
Java 在图形用户界面和桌面程序方面的发展可以说是一段悲伤的历史。Java 1.0 中图形用户界面(GUI)库的原始设计目标是让用户能在所有平台提供一个漂亮的界面。但遗憾的是,这个理想没有实现。相反,Java 1.0 AWT(抽象窗口工具包)在所有平台都表现平平,并且有诸多限制。你只能使用四种字体。另外,Java 1.0 AWT 编程模型也很笨拙且非面向对象。我的一个曾在 Java 设计期间工作过的学生道出了缘由:早期的 AWT 设计是在仅仅在一个月内构思、设计和实施的。不得不说这是一个“奇迹”,但同时更是“设计失败”的绝佳教材。
diff --git a/docs/book/00-On-Java-8.md b/docs/book/00-On-Java-8.md
index 37c23094..c11bde4d 100644
--- a/docs/book/00-On-Java-8.md
+++ b/docs/book/00-On-Java-8.md
@@ -53,7 +53,7 @@
本书出版自美国,版权所有,翻版必究。未经授权不得非法存储在检索系统中,或以电子,机械,影印,录制任何形式传输等。制造商和销售商使用商标用来区分其产品标识。如果这些名称出现在这本书中,并且出版商知道商标要求,则这些名称已经用大写字母或所有大写字母打印。
-Java 是甲骨文公司(Oracle. Inc.)的商标。Windows 95,Windows NT,Windows 2000,Windows XP,Windows 7,Windows 8和 Windows 10是微软公司(Microsoft Corporation)的商标。
+Java 是甲骨文公司(Oracle. Inc.)的商标。Windows 95,Windows NT,Windows 2000,Windows XP,Windows 7,Windows 8 和 Windows 10 是微软公司(Microsoft Corporation)的商标。
此处提及的所有其他产品名称和公司名称均为其各自所有者的财产。作者和出版商在编写本书时已经仔细校对过,但不作任何明示或暗示的保证,对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损失,我们不承担任何责任。
这本书是以平板电脑和计算机为载体的电子书,非传统纸质版书籍。
diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md
index d5f7e85e..be8f27e4 100644
--- a/docs/book/00-Preface.md
+++ b/docs/book/00-Preface.md
@@ -4,13 +4,12 @@
> 本书基于 Java 8 版本来教授当前 Java 编程的最优实践。
-此前,我的另一本 Java 书籍 *Thinking in Java,4th Edition*(《Java编程思想》 第 4 版 Prentice Hall 2006)依然适用于 Java 5 编程。Android 编程就是始于此语言版本。
+此前,我的另一本 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想》 第 4 版 Prentice Hall 2006)依然适用于 Java 5 编程。Android 编程就是始于此语言版本。
随着 Java 8 的出现,这门语言在许多地方发生了翻天覆地的变化。在新的版本中,代码的运用和实现上与以往不尽相同。这也促使了我时隔多年后再次创作了这本新书。《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站上补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。
与几年前我们依赖印刷媒体相比,YouTube,博客和 StackOverflow 等网站的出现让寻找答案变得简单。请结合这些学习途径和努力坚持下去。本书可作为编程入门书籍,同时也适用于想要扩展知识的在职程序员。每次在世界各地的演讲中,我都非常感谢 《*Thinking in Java*》 这本书给我带来的所有荣誉。它对于我重塑 [Reinventing Business](http://www.reinventing-business.com) 项目和促进交流是非常宝贵的。最后,写这本书的原因之一 希望这本书可以为我的这个项目众筹。似乎下一步要创建一个所谓的蓝绿色组织(Teal Organization)才合乎逻辑的。
-
## 教学目标
每章教授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。
@@ -22,12 +21,10 @@
2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意看到我的读者们因此能保持饶有兴趣地学习。
-3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95%的程序员都不需要取知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。
+3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要取知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。
4. 希望本书能为你打下坚实的基础,方便你将来学习更难的课程和书籍。
-
-
## 语言设计错误
每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错了什么还要糟糕。哎,每一种设语言/库的设计错误都会永久地嵌入在 Java 的发行版中。
@@ -48,60 +45,42 @@
新功能的设计与以往有很大不同。掌舵者开始重视程序员的编程经验。新功能的开发都是在努力使语言变得更好,而非仅仅停留在快速堆砌功能而不去深入研究它们的含义。甚至有些新特性的实现方式非常优雅(至少在 Java 约束下尽可能优雅)。
-我猜测可能是部分设计者的离开让他们意识到了这点。说真的,我没想到会有这些变化!因为这些原因吧,写这本书的体验要比以往好很多。Java 8 包含了一系列基础和重要的改进。遗憾的是,为了严格地“向后兼容”,我们不大可能看到戏剧性的变化,当然我希望我是错的。尽管如此,我很赞赏那些敢于自我颠覆,并为 Java 设定更好路线的人。第一次,对于自己所写的部分 Java 8 代码我终于可以说“赞!”
+我猜测可能是部分设计者的离开让他们意识到了这点。说真的,我没想到会有这些变化!因为这些原因,写这本书的体验要比以往好很多。Java 8 包含了一系列基础和重要的改进。遗憾的是,为了严格地“向后兼容”,我们不大可能看到戏剧性的变化,当然我希望我是错的。尽管如此,我很赞赏那些敢于自我颠覆,并为 Java 设定更好路线的人。第一次,对于自己所写的部分 Java 8 代码我终于可以说“赞!”
最后,本书所著时间似乎也还不错,因为 Java 8 引入的新功能已经强烈地影响了今后 Java 的编码方式。截止我在写这本书时,Java 9 似乎更专注于对语言底层的基础结构功能的重要更新,而非本书所关注的新编码方式。话说回来,得益于电子书出版形式的便捷,假如我发现本书有需要更新或添加的内容,我可以第一时间将新版本推送给现有读者。
-
-
## 测试用例
书中代码示例基于 Java 8 和 Gradle 编译构建,并且代码示例都保存在[这个自由访问的GitHub的仓库](https://github.com/BruceEckel/OnJava8-Examples) 中。我们需要内置的测试框架,以便于在每次构建系统时自动运行。否则,你将无法保证自己代码的可靠性。为了实现这一点,我创建了一个测试系统来显示和验证大多数示例的输出结果。这些输出结果我会附加在示例结尾的代码块中。有时仅显示必要的那几行或者首尾行。利用这种方式来改善读者的阅读和学习体验,同时也提供了一种验证示例正确性的方法。
-
-
## 普及性
Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你更容易找到工作。相关的培训材料,课程和其他可用的学习资源也很多。对于企业来说,招聘 Java 程序员相对容易。如果你不喜欢 Java 语言,那么最好不要拿他当作你谋生的工具,因为这种生活体验并不好。作为一家公司,在技术选型前一定不要单单只考虑 Java 程序员好招。每种语言都有其适用的范围,有可能你们的业务更适用于另一种编程语言来达到事半功倍的效果。如果你真的喜欢 Java,那么欢迎你。希望这本书能丰富你的编程经验!
-
-
## 关于安卓
本书基于 Java 8 版本。如果你是 Andriod 程序员,请务必学习 Java 5。在《On Java 8》出版的时候,我的另一本基于 Java 5 的著作 *Thinking in Java 4th Edition*(《Java编程思想》第四版)已经可以在[www.OnJava8.com](http://www.OnJava8.com)上免费下载了。此外,还有许多其他专用于 Andriod 编程的资源。
-
-
-
## 电子版权声明
-《On Java 8》仅提供电子版,并且仅通过 [www.OnJava8.com](http://www.OnJava8.com) 提供。任何未经 授权的其他来源或流传送机构都是非法的。本作品受版权保护!未经许可,请勿通过以任何方式分享或发布。你可以使用这些示例进行教学,只要不对本书非法重新出版。有关完整详细信息,请参阅示例分发中的 Copyright.txt 文件。对于视觉障碍者,电子版本有可搜索性,字体大小调整或文本到语音等诸多好处。
+《On Java 8》仅提供电子版,并且仅通过 [www.OnJava8.com](http://www.OnJava8.com) 提供。任何未经 授权的其他来源或流传送机构都是非法的。本作品受版权保护!未经许可,请勿通过以任何方式分享或发布。你可以使用这些示例进行教学,只要不对本书非法重新出版。有关完整详细信息,请参阅示例分发中的 Copyright.txt 文件。对于视觉障碍者,电子版本有可搜索性,字体大小调整或文本到语音等诸多好处。
任何购买这本书的读者,还需要一台计算机来运行和写作代码。另外电子版在计算机上和移动设备上的显示效果俱佳,推荐使用平板设备阅读。相比购买传统纸质版的价格,平板电脑价格都足够便宜。在床上阅读电子版比看这样一本厚厚的实体书要方便得多。起初你可能会有些不习惯,但我相信很快你就会发现它带来的优点远胜过不适。我已经走过这个阶段,Google Play 图书的浏览器阅读体验非常好,包括在 Linux 和 iOS 设备上。作为一次尝试,我决定尝试通过 Google 图书进行出版。
**注意**:在撰写本文时,通过 Google Play 图书网络浏览器应用阅读图书虽然可以忍受,但体验还是有点差强人意,我强烈推荐读者们使用平板电脑来阅读。
-
-
-
## 版本说明
本书采用 [Pandoc](http://pandoc.org) 风格的 Markdown 编写,使用 Pandoc 生成 ePub v3 格式。
正文字体为 Georgia,标题字体为 Verdana。 代码字体使用的 Ubuntu Mono,因为它特别紧凑,单行能容纳更多的代码。 我选择将代码内联(而不是将列表放入图像,参照其他书籍),因为我觉得这个功能很重要:让代码块能适应字体大小得改变而改变(否则,买电子版,还图什么呢?)。
-书中的提取,编译和测试代码示例的构建过程都是自动化的。所有自动化操作都是通过我在 Python 3中编写的程序来实现的。
-
-
-
+书中的提取,编译和测试代码示例的构建过程都是自动化的。所有自动化操作都是通过我在 Python 3 中编写的程序来实现的。
## 封面设计
《On Java 8》的封面是根据 W.P.A.(Works Progress Administration 1935年至1943年美国大萧条期间的一个巨大项目,它使数百万失业人员重新就业)的马赛克创作的。它还让我想起了《绿野仙踪》(*The Wizard of Oz*)系列丛书中的插图。 我的好朋友、设计师丹 *Daniel Will-Harris*([www.will-harris.com](http://www.will-harris.com))和我都喜欢这个形象。
-
-
-
## 感谢的人
感谢 *Domain-Driven Design*(《领域驱动设计》 )的作者 *Eric Evans* 建议书名,以及其他新闻组校对的帮助。
diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md
index ace67d37..32db8473 100644
--- a/docs/book/01-What-is-an-Object.md
+++ b/docs/book/01-What-is-an-Object.md
@@ -1,10 +1,6 @@
-[TOC]
-
-
# 第一章 对象的概念
-
> “我们没有意识到惯用语言的结构有多大的力量。可以毫不夸张地说,它通过语义反应机制奴役我们。语言表现出来并在无意识中给我们留下深刻印象的结构会自动投射到我们周围的世界。” -- Alfred Korzybski (1930)
计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。
@@ -23,7 +19,7 @@
面向对象的程序设计在此基础上跨出了一大步,程序员可利用一些工具表达“问题空间”内的元素。由于这种表达非常具有普遍性,所以不必受限于特定类型的问题。我们将问题空间中的元素以及它们在解决方案空间的表示称作“对象”(**Object**)。当然,还有一些在问题空间没有对应的对象体。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。所以当你在阅读描述解决方案的代码时,也是在阅读问题的表述。与我们以前见过的相比,这无疑是一种更加灵活、更加强大的语言抽象方法。总之,OOP 允许我们根据问题来描述问题,而不是根据运行解决方案的计算机。然而,它仍然与计算机有联系,每个对象都类似一台小计算机:它们有自己的状态并且可以进行特定的操作。这与现实世界的“对象”或者“物体”相似:它们都有自己的特征和行为。
-Smalltalk 作为第一个成功的面向对象并影响了 Java的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:
+Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:
> 1. **万物皆对象**。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。
> 2. **程序是一组对象,通过消息传递来告知彼此该做什么**。要请求调用一个对象的方法,你需要向该对象发送消息。
@@ -33,14 +29,13 @@ Smalltalk 作为第一个成功的面向对象并影响了 Java的程序设计
*Grady Booch* 提供了对对象更简洁的描述:一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。
-
## 接口
亚里士多德(*Aristotle*)大概是第一个认真研究“类型”的哲学家,他曾提出过“鱼类和鸟类”这样的概念。所有对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。这种思想被首次应用于第一个面向对象编程语言 Simula-67,它在程序中使用基本关键字 **class** 来引入新的类型(class 和 type 通常可互换使用,有些人对它们进行了进一步区分,他们强调 type 决定了接口,而 class 是那个接口的一种特殊实现方式)。
Simula 是一个很好的例子。正如这个名字所暗示的,它的作用是“模拟”(Simulate)类似“银行出纳员”这样的经典问题。在这个例子里,我们有一系列出纳员、客户、帐号、交易和货币单位等许多"对象”。每类成员(元素)都具有一些通用的特征:每个帐号都有一定的余额;每名出纳都能接收客户的存款;等等。与此同时,每个成员都有自己的状态;每个帐号都有不同的余额;每名出纳都有一个名字。所以在计算机程序中,能用独一无二的实体分别表示出纳员、客户、帐号以及交易。这个实体便是“对象”,而且每个对象都隶属一个特定的“类”,那个类具有自己的通用特征与行为。
-因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到“type”这个词的时候,请同时想到 `class`;反之亦然。
+因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。
创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战性就是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。
@@ -55,13 +50,12 @@ Light lt = new Light();
lt.on();
```
-在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个**Light**类型的对象,再用等号将其赋给引用。
+在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。
为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。
上图遵循 **UML**(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 **UML** 设计图中显示,因此中间部分未显示,如本例所示。如果你只对类名感兴趣,则也不需要显示方法信息。
-
## 服务提供
在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。你的程序本身将为用户提供服务,并且它能通过调用其他对象提供的服务来实现这一点。我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。
@@ -70,12 +64,11 @@ lt.on();
我们可以将这些问题一一分解,抽象成一组服务。软件设计的基本原则是高内聚:每个组件的内部作用明确,功能紧密相关。然而经常有人将太多功能塞进一个对象中。例如:在支票打印模块中,你需要设计一个可以同时读取文本格式又能正确识别不同打印机型号的对象。正确的做法是提供三个或更多对象:一个对象检查所有排版布局的目录;一个或一组可以识别不同打印机型号的对象展示通用的打印界面;第三个对象组合上述两个服务来完成任务。这样,每个对象都提供了一组紧密的服务。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。
-
## 封装
我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。当应用程序员调用研发程序员开发的工具类时,双方建立了关系。应用程序员通过使用现成的工具类组装应用程序或者构建更大的工具库。如果工具类的创建者将类的内部所有信息都公开给调用者,那么有些使用规则就不容易被遵守。因为前者无法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种情况的发生。
-因此,使用访问控制的原因有以下2点:
+因此,使用访问控制的原因有以下两点:
1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);
@@ -83,14 +76,13 @@ lt.on();
Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。
- 1. `public` (公开) 表示任何人都可以访问和使用该元素;
+ 1. `public`(公开)表示任何人都可以访问和使用该元素;
- 2. `private` (私有) 除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;
+ 2. `private`(私有)除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;
- 3. `protected` (受保护) 类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;
-
- 4. `default` (默认) 如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。
+ 3. `protected`(受保护)类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;
+ 4. `default`(默认)如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。
## 复用
@@ -100,7 +92,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
* **组合**(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。
-* **聚合**(Aggregation) 动态的 **组合**。
+* **聚合**(Aggregation)动态的**组合**。

@@ -118,8 +110,6 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。
-
-

这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。
@@ -136,21 +126,20 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
有两种方法可以区分新的派生类与原始的基类。第一种方法很简单:在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。然而,还是要仔细考虑是否在基类中也要有这些额外的方法。这种设计的发现与迭代过程在面向对象程序设计中会经常发生。
-尽管继承有时意味着你要在接口中添加新方法(尤其是在以**extends**关键字表示继承的Java中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖(overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。
+尽管继承有时意味着你要在接口中添加新方法(尤其是在以 **extends** 关键字表示继承的 Java 中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。
### "是一个"与"像是一个"的关系
-对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作"纯粹替代",也经常被称作"替代原则"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说"圆是一个形状"。判断是否继承,就看在你的类之间有无这种is-a关系。
+对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作"纯粹替代",也经常被称作"替代原则"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说"圆是一个形状"。判断是否继承,就看在你的类之间有无这种 is-a 关系。
有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。

-以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为"温度控制系统",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。
+以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为"温度控制系统",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。
当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。
-
## 多态
我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。在上个“形状”的例子中,“方法”(method)操纵的是通用“形状”,而不关心它们是“圆”、“正方形”、“三角形”还是某种尚未定义的形状。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。
@@ -167,7 +156,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开
通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用**后期绑定**的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。
-为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用**virtual**关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。
+为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 **virtual** 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。
为了演示多态性,我们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。
@@ -213,7 +202,7 @@ 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()` 编译代码时,它并不知道自己要操作的准确类型是什么。
@@ -223,7 +212,7 @@ void doSomething(Shape shape) {
## 单继承结构
-自从 C++ 引入以来,一个OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。
+自从 C++ 引入以来,一个 OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。
Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。
@@ -233,7 +222,6 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共
由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如[异常处理](#异常处理)。同时,这也让我们的编程具有更大的灵活性。
-
## 集合
通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?
@@ -262,7 +250,6 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共
泛型的应用,让 Java 的许多标准库和组件都发生了改变。在本书的代码示例中,你也会经常看到泛型的身影。
-
## 对象创建与生命周期
我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。
@@ -285,7 +272,6 @@ Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字
Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其他方面)。垃圾收集器知道对象什么时候不再被使用并且自动释放内存。结合单继承和仅可在堆中创建对象的机制,Java 的编码过程比用 C++ 要简单得多。我们所要做的决定和要克服的障碍也会少很多!
-
## 异常处理
自编程语言被发明以来,程序的错误处理一直都是个难题。因为很难设计出一个好的错误处理方案,所以许多编程语言都忽略了这个问题,把这个问题丢给了程序类库的设计者。他们提出了在许多情况下都可以工作但很容易被规避的半途而废的措施,通常只需忽略错误。多数错误处理方案的主要问题是:它们依赖程序员之间的约定俗成而不是语言层面的限制。换句话说,如果程序员赶时间或没想起来,这些方案就很容易被忘记。
@@ -296,7 +282,6 @@ Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不
Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。
-
## 本章小结
面向过程程序包含数据定义和函数调用。要找到程序的意图,你必须要在脑中建立一个模型,弄清函数调用和更底层的概念。这些程序令人困扰,因为它们的表示更多地面向计算机而不是我们要解决的问题,这就是我们在设计程序时需要中间表示的原因。OOP 在面向过程编程的基础上增加了许多新的概念,所以有人会认为使用 Java 来编程会比同等的面向过程编程要更复杂。在这里,我想给大家一个惊喜:通常按照 Java 规范编写的程序会比面向过程程序更容易被理解。
@@ -306,5 +291,4 @@ Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就
OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。
-
-
+
\ No newline at end of file
From 62c0e8d2210a828ed42c104692b58f83b78cb6e0 Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sat, 17 Aug 2019 20:33:26 +0800
Subject: [PATCH 035/449] update format 02-04
---
...2-Installing-Java-and-the-Book-Examples.md | 11 +--
docs/book/03-Objects-Everywhere.md | 68 ++++++----------
docs/book/04-Operators.md | 81 +++++--------------
3 files changed, 46 insertions(+), 114 deletions(-)
diff --git a/docs/book/02-Installing-Java-and-the-Book-Examples.md b/docs/book/02-Installing-Java-and-the-Book-Examples.md
index 5dbc8b1f..b9633d66 100644
--- a/docs/book/02-Installing-Java-and-the-Book-Examples.md
+++ b/docs/book/02-Installing-Java-and-the-Book-Examples.md
@@ -1,23 +1,21 @@
[TOC]
-# 第二章 安装Java和本书用例
+# 第二章 安装 Java 和本书用例
现在,我们来为这次阅读之旅做些准备吧!
在开始学习 Java 之前,你必须要先安装好 Java 和本书的源代码示例。因为考虑到可能有“专门的初学者”从本书开始学习编程,所以我会详细地教你如何使用命令行。 如果你已经有此方面的经验了,可以跳过这段安装说明。如果你对此处描述的任何术语或过程仍不清楚,还可以通过 [Google](https://google.com/) 搜索找到答案。具体的问题或困难请试着在 [StackOverflow](https://stackoverflow.com/) 上提问。或者去 [YouTube](https://youtube.com) 看有没有相关的安装说明。
-
## 编辑器
首先你需要安装一个编辑器来创建和修改本书用例里的 Java 代码。有可能你还需要使用编辑器来更改系统配置文件。
-相比一些重量级的 IDE(Integrated Development Environments,集成开发环境),如Eclipse、NetBeans和IntelliJ IDEA (译者注:做项目强烈推荐IDEA),编辑器是一种更纯粹的文本编辑器。如果你已经有了一个用着顺手的 IDE,那就可以直接用了。为了方便后面的学习和统一下教学环境,我推荐大家使用 Atom 这个编辑器。大家可以在 [atom.io](https://atom.io) 上下载。
+相比一些重量级的 IDE(Integrated Development Environments,集成开发环境),如 Eclipse、NetBeans 和 IntelliJ IDEA (译者注:做项目强烈推荐IDEA),编辑器是一种更纯粹的文本编辑器。如果你已经有了一个用着顺手的 IDE,那就可以直接用了。为了方便后面的学习和统一下教学环境,我推荐大家使用 Atom 这个编辑器。大家可以在 [atom.io](https://atom.io) 上下载。
Atom 是一个免费开源、易于安装且跨平台(支持 Window、Mac和Linux)的文本编辑器。内置支持 Java 文件。相比 IDE 的厚重,它比较轻量级,是学习本书的理想工具。Atom 包含了许多方便的编辑功能,相信你一定会爱上它!更多关于 Atom 使用的细节问题可以到它的网站上寻找。
还有很多其他的编辑器。有一种亚文化的群体,他们热衷于争论哪个更好用!如果你找到一个你更喜欢的编辑器,换一种使用也没什么难度。重要的是,你要找一个用着舒服的。
-
## Shell
如果你之前没有接触过编程,那么有可能对 Shell(命令行窗口) 不太熟悉。shell 的历史可以追溯到早期的计算时代,当时在计算机上的操作是都通过输入命令进行的,计算机通过回显响应。所有的操作都是基于文本的。
@@ -91,7 +89,7 @@ Linux: 在 home 目录打开 Shell。
```
-## Java安装
+## Java 安装
为了编译和运行代码示例,首先你必须安装 JDK(Java Development Kit,JAVA 软件开发工具包)。本书中采用的是 JDK 8。
@@ -150,7 +148,6 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
```
如果提示命令找不到或者无法被识别,请根据安装说明重试;如果还不行,尝试到 [StackOverflow](https://stackoverflow.com/search?q=installing+java) 寻找答案。
-
## 安装和运行代码示例
当 Java 安装完毕,下一步就是安装本书的代码示例了。安装步骤所有平台一致:
@@ -173,7 +170,7 @@ Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
**注意**: 第一次运行 gradlew 命令时必须连接互联网。
-**Gradle基础任务**
+**Gradle 基础任务**
本书构建的大量 Gradle 任务都可以自动运行。Gradle 使用约定大于配置的方式,简单设置即可具备高可用性。本书中“一起去骑行”的某些任务不适用于此或无法执行成功。以下是你通常会使用上的 Gradle 任务列表:
diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md
index bd96286f..8884e234 100644
--- a/docs/book/03-Objects-Everywhere.md
+++ b/docs/book/03-Objects-Everywhere.md
@@ -33,39 +33,36 @@ Java 利用万物皆对象的思想和单一一致的语法方式来简化问题
Java 语法允许我们使用带双引号的文本内容来初始化字符串。同样,其他类型的对象也有相应的初始化方式。
-
## 对象创建
-“引用”用来关联“对象”。在 Java 中,通常我们使用`new`操作符来创建一个新对象。`new`关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例:
+“引用”用来关联“对象”。在 Java 中,通常我们使用`new`操作符来创建一个新对象。`new` 关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例:
```java
String s = new String("asdf");
```
-以上展示了字符串对象的创建过程,以及如何初始化生成字符串。除了 **String** 类型以外,Java 本身自带了许多现成的数据类型。除此之外,我们还可以创建自己的数据类型。事实上,这是Java程序设计中的一项基本行为。在本书后面的学习中将会接触到。
+以上展示了字符串对象的创建过程,以及如何初始化生成字符串。除了 **String** 类型以外,Java 本身自带了许多现成的数据类型。除此之外,我们还可以创建自己的数据类型。事实上,这是 Java 程序设计中的一项基本行为。在本书后面的学习中将会接触到。
-
### 数据存储
那么,程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据:
-1. **寄存器** (Registers) 最快的存储区域,位于CPU内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。
+1. **寄存器**(Registers)最快的存储区域,位于 CPU 内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。
-2. **栈内存**(Stack) 存在于常规内存 RAM (随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。
+2. **栈内存**(Stack)存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。
-3. **堆内存**(Heap) 这是一种通用的内存池(也在 RAM区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。
+3. **堆内存**(Heap)这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。
-4. **常量存储** (Constant storage) 常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 [^3]。
-
-5. **非 RAM 存储** (Non-RAM storage) 数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。
+4. **常量存储**(Constant storage)常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 [^3]。
+5. **非 RAM 存储**(Non-RAM storage)数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。
### 基本类型的存储
-有一组类型在 Java 中使用频率很高,它们需要特殊对待,这就是 Java 的基本类型。之所以这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在堆内存中的, 以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法, Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效。
+有一组类型在 Java 中使用频率很高,它们需要特殊对待,这就是 Java 的基本类型。之所以这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法,Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量直接存储"值",并置于栈内存中,因此更加高效。
-Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。
+Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。
| 基本类型 | 大小 | 最小值 | 最大值 | 包装类型 |
| :------: | :------: | :------: | :------: | :------: |
@@ -99,7 +96,6 @@ char c = ch;
个中原因将在以后的章节里解释。
-
### 高精度数值
在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。
@@ -112,10 +108,8 @@ BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的
关于这两个类的详细信息,请参考 JDK 官方文档。
-
### 数组的存储
-
许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。
Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。(并且 Java 经常可以优化这些操作)。
@@ -125,10 +119,8 @@ Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程
我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。
-
## 代码注释
-
Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。注意,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到:
```java
@@ -138,7 +130,7 @@ Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以
*/
```
-但请记住, `/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面这样也是没有区别的。
+但请记住,`/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面这样也是没有区别的。
```java
/* 这是跨越多
@@ -154,12 +146,12 @@ Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以
## 对象清理
-在一些编程语言中,管理变量的生命周期需要大量的工作。一个变量需要存活多久?如果我们想销毁它,应该什么时候去做呢?变量生命周期的混乱会导致许多bug,本小结向你介绍 Java 是如何通过释放存储来简化这个问题的。
+在一些编程语言中,管理变量的生命周期需要大量的工作。一个变量需要存活多久?如果我们想销毁它,应该什么时候去做呢?变量生命周期的混乱会导致许多 bug,本小结向你介绍 Java 是如何通过释放存储来简化这个问题的。
### 作用域
-大多数程序语言都有作用域的概念。作用域决定了在该范围内定义的变量名的可见性和生存周期。在 C、C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。例如:
+大多数程序语言都有作用域的概念。作用域决定了在该范围内定义的变量名的可见性和生存周期。在 C、 C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。例如:
```java
{
@@ -188,7 +180,6 @@ Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易
在上例中, Java 编译器会在提示变量 x 已经被定义过了。因此,在 C/C++ 中将一个较大作用域的变量"隐藏"起来的做法,在 Java 中是不被允许的。 因为 Java 的设计者认为这样做会导致程序混乱。
-
### 对象作用域
Java 对象与基本类型具有不同的生命周期。当我们使用 `new` 关键字来创建 Java 对象时,它的生命周期将会超出作用域。因此,下面这段代码示例:
@@ -199,14 +190,14 @@ Java 对象与基本类型具有不同的生命周期。当我们使用 `new`
}
// 作用域终点
```
+
上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。在后面的章节中,我们还会学习怎么在编程中传递和复制对象的引用。
-只要你需要, `new` 出来的对象就会一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在使用完它们之后,将其销毁。
+只要你需要,`new` 出来的对象就会一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在使用完它们之后,将其销毁。
那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?答案是:Java 的垃圾收集器会检查所有 `new` 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。
-
## 类的创建
### 类型
@@ -228,7 +219,6 @@ ATypeName a = new ATypeName();
到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。
-
### 属性
当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和属性(field)。类的属性可以是基本类型,也可以是引用类型。如果类的属性是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其属性的空间。通常,属性不在对象间共享。下面是一个具有某些属性的类的代码示例:
@@ -264,7 +254,6 @@ class DataOnly {
你可以用这种方式嵌套许多对象(尽管这样的设计会带来混乱)。
-
### 基本类型默认值
如果类的成员变量(属性)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。
@@ -280,7 +269,7 @@ class DataOnly {
| float | 0.0f |
| double | 0.0d |
-这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的属性始终能被初始化(在C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。
+这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的属性始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。
这种默认值的赋予并不适用于局部变量 —— 那些不属于类的属性的变量。 因此,若在方法中定义的基本类型数据,如下:
@@ -290,7 +279,6 @@ class DataOnly {
这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步, Java 将会提示我们“编译时错误,该变量可能尚未被初始化”。 这一点做的比 C++ 更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。
-
### 方法使用
@@ -316,7 +304,7 @@ Java 中的方法只能作为类的一部分创建。它只能被对象所调用
[对象引用].[方法名](参数1, 参数2, 参数3);
```
-若方法不带参数,例如一个对象引用 `a` 的方法 `f` 不带参数并返回 **int** 型结果,我们可以如下表示:
+若方法不带参数,例如一个对象引用 `a` 的方法 `f` 不带参数并返回 **int** 型结果,我们可以如下表示:
```java
int x = a.f();
@@ -325,7 +313,6 @@ int x = a.f();
上例中方法 `f` 的返回值类型必须和变量 `x` 的类型兼容 。调用方法的行为有时被称为向对象发送消息。面向对象编程可以总结为:向对象发送消息。
-
#### 参数列表
方法参数列表指定了传递给方法的信息。正如你可能猜到的,这些信息就像 Java 中的其他所有信息 ,以对象的形式传递。参数列表必须指定每个对象的类型和名称。同样,我们并没有直接处理对象,而是在传递对象引用 [^5] 。但是引用的类型必须是正确的。如果方法需要 String 参数,则必须传入 String,否则编译器将报错。
@@ -336,7 +323,7 @@ int storage(String s) {
}
```
-此方法计算并返回某个字符串所占的字节数。参数 `s` 的类型为 **String** 。将 s 传递给 `storage()` 后,我们可以把它看作和任何其他对象一样,可以向它发送消息。在这里,我们调用 `length()` 方法,它是一个 String 方法,返回字符串中的字符数。字符串中每个字符的大小为16位或2个字节。你还看到了 **return** 关键字,它执行两项操作。首先,它意味着“方法执行结束”。其次,如果方法有返回值,那么该值就紧跟 **return** 语句之后。这里,返回值是通过计算
+此方法计算并返回某个字符串所占的字节数。参数 `s` 的类型为 **String** 。将 s 传递给 `storage()` 后,我们可以把它看作和任何其他对象一样,可以向它发送消息。在这里,我们调用 `length()` 方法,它是一个 String 方法,返回字符串中的字符数。字符串中每个字符的大小为 16 位或 2 个字节。你还看到了 **return** 关键字,它执行两项操作。首先,它意味着“方法执行结束”。其次,如果方法有返回值,那么该值就紧跟 **return** 语句之后。这里,返回值是通过计算
```java
s.length() * 2
@@ -365,7 +352,6 @@ void nothing2() {
上面的描述可能会让你感觉程序只不过是一堆包含各种方法的对象,在这些方法中,将对象作为参数并发送消息给其他对象。大部分情况下确实如此。但在下一章的运算符中我们将会学习如何在方法中做出决策来完成更底层、详细的工作。对于本章,知道如何发送消息就够了。
-
## 程序编写
@@ -377,11 +363,11 @@ void nothing2() {
Java 采取了一种新的方法避免了以上这些问题:为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我将我的 foibles 类库命名为 com.mindviewinc.utility.foibles。反转域名后,`.` 用来代表子目录的划分。
-在 Java 1.0 和 Java 1.1 中,域扩展名 com,edu,org,net 等按惯例大写,因此类库中会出现这样类似的名称:Com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。
+在 Java 1.0 和 Java 1.1 中,域扩展名 com、 edu、 org 和 net 等按惯例大写,因此类库中会出现这样类似的名称:com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。
使用反向 URL 是一种新的命名空间方法,在此之前尚未有其他语言这么做过。Java 中有许多这些“创造性”地解决问题的方法。正如你想象,如果我们未经测试就添加一个功能并用于生产,那么在将来发现该功能的问题再想纠正,通常为时已晚(有些错误太严重了就得从语言中删除新功能。)
-使用反向 URL 将命名空间与文件路径相关联不会导致BUG,但它却给源代码管理带来麻烦。例如在 `com.mindviewinc.utility.foibles` 这样的目录结构中,我们创建了 `com`、`mindviewinc` 空目录。它们存在的唯一目的就是用来表示这个反向的 URL。
+使用反向 URL 将命名空间与文件路径相关联不会导致BUG,但它却给源代码管理带来麻烦。例如在 `com.mindviewinc.utility.foibles` 这样的目录结构中,我们创建了 `com` 和 `mindviewinc` 空目录。它们存在的唯一目的就是用来表示这个反向的 URL。
这种方式似乎为我们在编写 Java 程序中的某个问题打开了大门。空目录填充了深层次结构,它们不仅用于表示反向 URL,还用于捕获其他信息。这些长路径基本上用于存储有关目录中的内容的数据。如果你希望以最初设计的方式使用目录,这种方法可以从“令人沮丧”到“令人抓狂”,对于生产级的 Java 代码,你必须使用专门为此设计的 IDE 来管理代码。例如 NetBeans,Eclipse 或 IntelliJ IDEA。实际上,这些 IDE 都为我们管理和创建深层次空目录结构。
@@ -390,7 +376,6 @@ Java 采取了一种新的方法避免了以上这些问题:为一个类库生
#### 使用其他组件
-
无论何时在程序中使用预先定义好的类,编译器都必须找到该类。最简单的情况下,该类存在于被调用的源代码文件中。此时我们使用该类 —— 即使该类在文件的后面才会被定义(Java 消除了所谓的“前向引用”问题)。而如果一个类位于其他文件中,又会怎样呢?你可能认为编译器应该足够智能去找到它,但这样是有问题的。想象一下,假如你要使用某个类,但目录中存在多个同名的类(可能用途不同)。或者更糟糕的是,假设你正在编写程序,在构建过程中,你想将某个新类添加到类库中,但却与已有的类名称冲突。
要解决此问题,你必须通过使用 **import** 关键字来告诉 Java 编译器具体要使用的类。**import** 指示编译器导入一个包,也就是一个类库(在其他语言中,一个库不仅包含类,还可能包括函数和数据,但请记住 Java 中的所有代码都必须写在类里)。大多数时候,我们都在使用 Java 标准库中的组件。有了这些构件,你就不必写一长串的反转域名。例如:
@@ -408,7 +393,6 @@ import java.util.*;
本书中的示例很小,为简单起见,我们通常会使用 `.*` 形式略过导入。然而,许多教程书籍都会要求程序员逐一导入每个类。
-
#### static关键字
类是对象的外观及行为方式的描述。通常只有在使用 `new` 创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。
@@ -470,7 +454,6 @@ Incrementable.increment();
相比非静态的对象,`static` 属性改变了数据创建的方式。同样,当 `static` 关键字修饰方法时,它允许我们无需创建对象就可以直接通过类的引用来调用该方法。正如我们所知,`static` 关键字的这些特性对于应用程序入口点的 `main()` 方法尤为重要。
-
## 小试牛刀
最后,我们开始编写第一个完整的程序。我们使用 Java 标准库中的 **Date** 类来展示一个字符串和日期。
@@ -566,7 +549,6 @@ java.io.tmpdir=C:\Users\Bruce\AppData\Local\Temp\
`main()` 方法中的第一行会输出所有的系统属性,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的属性。该例中我们使用到了 **user.name** 和 **java.library.path**。
-
### 编译和运行
要编译和运行本书中的代码示例,首先必须具有 Java 编程环境。 第二章的示例中描述了安装过程。如果你遵循这些说明,那么你将会在不受 Oracle 的限制的条件下用到 Java 开发工具包(JDK)。如果你使用其他开发系统,请查看该系统的文档以确定如何编译和运行程序。 第二章还介绍了如何安装本书的示例。
@@ -589,15 +571,14 @@ java HelloDate
## 编码风格
-
Java 编程语言编码规范(Code Conventions for the Java Programming Language)[^10] 要求类名的首字母大写。 如果类名是由多个单词构成的,则每个单词的首字母都应大写(不采用下划线来分隔)例如:
```java
class AllTheColorsOfTheRainbow {
// ...
+}
```
-
有时称这种命名风格叫“驼峰命名法”。对于几乎所有其他方法,字段(成员变量)和对象引用名都采用驼峰命名的方式,但是它们的首字母不需要大写。代码示例:
```java
@@ -613,13 +594,10 @@ class AllTheColorsOfTheRainbow {
在 Oracle 的官方类库中,花括号的位置同样遵循和本书中上述示例相同的规范。
-
## 本章小结
-
本章向你展示了简单的 Java 程序编写以及该语言相关的基本概念。到目前为止,我们的示例都只是些简单的顺序执行。在接下来的两章里,我们将会接触到 Java 的一些基本操作符,以及如何去控制程序执行的流程。
-
[^1]: 这里可能有争议。有人说这是一个指针,但这假定了一个潜在的实现。此外,Java 引用的语法更类似于 C++ 引用而非指针。在 《Thinking in Java》 的第 1 版中,我发明了一个新术语叫“句柄”(handle),因为 C++ 引用和Java 引用有一些重要的区别。作为一个从 C++ 的过来人,我不想混淆 Java 可能的最大受众 —— C++ 程序员。在《Thinking in Java》的第 2 版中,我认为“引用”(reference)是更常用的术语,从 C++ 转过来的人除了引用的术语之外,还有很多东西需要处理,所以他们不妨双脚都跳进去。但是,也有些人甚至不同意“引用”。在某书中我读到一个观点:Java 支持引用传递的说法是完全错误的,因为 Java 对象标识符(根据该作者)实际上是“对象引用”(object references),并且一切都是值传递。所以你不是通过引用传递,而是“通过值传递对象引用。人们可以质疑我的这种解释的准确性,但我认为我的方法简化了对概念的理解而又没对语言造成伤害(嗯,语言专家可能会说我骗你,但我会说我只是对此进行了适当的抽象。)
[^2]: 大多数微处理器芯片都有额外的高速缓冲存储器,但这是按照传统存储器而不是寄存器。
@@ -628,7 +606,7 @@ class AllTheColorsOfTheRainbow {
[^4]: 静态方法,我们很快就能接触到,它可以在没有对象的情况下直接被类调用。
-[^5]: 通常除了前面提到的“特殊”数据类型 boolean,char,byte,short,int,long,float 和 double。通常来说,传递对象就意味者传递对象的引用。
+[^5]: 通常除了前面提到的“特殊”数据类型 boolean、 char、 byte、 short、 int、 long、 float 和 double。通常来说,传递对象就意味者传递对象的引用。
[^6]: 静态方法在使用之前不需要创建对象,因此它们不能直接调用非静态的成员或方法(因为非静态成员和方法必须要先实例化为对象才可以被使用)。
@@ -639,6 +617,6 @@ class AllTheColorsOfTheRainbow {
[^9]: 对于本书中编译和运行命令行的每个程序,你可能还需要设置 CLASSPATH 。
[^10]: 为了保持本书的代码排版紧凑,我并没完全遵守规范,但我尽量会做到符合 Java 标准。
-
-
+
+
\ No newline at end of file
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index 05ed80d5..6da34a35 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -3,27 +3,22 @@
# 第四章 运算符
-
>运算符操纵数据。
Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。
如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。
-
-## 使用说明
-
+## 使用 Java 运算符
-运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`,减法 `-`,乘法 `*`,除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
+运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
几乎所有运算符都只能操作基本类型(Primitives)。唯一的例外是 `=`、`==` 和 `!=`,它们能操作所有对象(这也是令人混淆的一个地方)。除此以外,**String** 类支持 `+` 和 `+=`。
-
## 优先级
-
运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。代码示例:
```java
@@ -40,7 +35,7 @@ public class Precedence {
}
```
- 输出结果:
+输出结果:
```
a = 5
@@ -52,13 +47,11 @@ public class Precedence {
我们注意到,在 `System.out.println()` 语句中使用了 `+` 运算符。 但是在这里 `+` 代表的意思是字符串连接符。编译器会将 `+` 连接的非字符串尝试转换为字符串。上例中的输出结果说明了 a 和 b 都已经被转化成了字符串。
-
## 赋值
+运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 = A)。
-运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量( A = 4 ),但不可将任何东西赋给一个常数(比如不能 4 = A)。
-
-基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。举个例子,a = b ,如果 b 是基本类型,那么 赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。
+基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。
如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:
@@ -96,7 +89,7 @@ public class Assignment {
3: t1.level: 27, t2.level: 27
```
-这是一个简单的 `Tank` 类,在 `main()` 方法创建了 2 个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。
+这是一个简单的 `Tank` 类,在 `main()` 方法创建了两个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。
这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:
@@ -143,14 +136,12 @@ public class PassObject {
在许多编程语言中,方法 `f()` 似乎会在内部复制其参数 **Letter y**。但是一旦传递了一个引用,那么实际上 `y.c ='z';` 是在方法 `f()` 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:[对象传递和返回](./Appendix-Passing-and-Returning-Objects.md)。意识到这一点,我们可以警惕类似的陷阱。
-
## 算术运算符
Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 `+`、减号 `-`、除号 `/`、乘号 `*` 以及取模 `%`(从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。 可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。更多代码示例:
-
```java
// operators/MathOps.java
// The mathematical operators
@@ -233,7 +224,6 @@ u /= v : 9.940527
为了生成随机数字,程序首先创建一个 **Random** 对象。不带参数的 **Random** 对象会利用当前的时间用作随机数生成器的“种子”(seed),从而为程序的每次执行生成不同的输出。在本书的示例中,重要的是每个示例末尾的输出尽可能一致,以便可以使用外部工具进行验证。所以我们通过在创建 **Random** 对象时提供种子(随机数生成器的初始化值,其始终为特定种子值产生相同的序列),让程序每次执行都生成相同的随机数,如此以来输出结果就是可验证的 [^1]。 若需要生成随机值,可删除代码示例中的种子参数。该对象通过调用方法 `nextInt()` 和 `nextFloat()`(还可以调用 `nextLong()` 或 `nextDouble()`),使用 **Random** 对象生成许多不同类型的随机数。`nextInt()` 的参数设置生成的数字的上限,下限为零,为了避免零除的可能性,结果偏移1。
-
### 一元加减运算符
一元加 `+` 减 `-` 运算符的操作和二元是相同的。编译器可自动识别使用何种方式解析运算:
@@ -257,10 +247,8 @@ x = a * (-b);
一元减号可以得到数据的负值。一元加号的作用相反,不过它唯一能影响的就是把较小的数值类型自动转换为 **int** 类型。
-
## 递增和递减
-
和 C 语言类似,Java 提供了许多快捷运算方式。快捷运算可使代码可读性,可写性都更强。其中包括递增 `++` 和递减 `--`,意为“增加或减少一个单位”。举个例子来说,假设 a 是一个 **int** 类型的值,则表达式 `++a` 就等价于 `a = a + 1`。 递增和递减运算符不仅可以修改变量,还可以生成变量的值。
每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀”和“后缀”。“前递增”表示 `++` 运算符位于变量或表达式的前面;而“后递增”表示 `++` 运算符位于变量的后面。类似地,“前递减”意味着 `--` 运算符位于变量的前面;而“后递减”意味着 `--` 运算符位于变量的后面。对于前递增和前递减(如 `++a` 或 `--a`),会先执行递增/减运算,再返回值。而对于后递增和后递减(如 `a++` 或 `a--`),会先返回值,再执行递增/减运算。代码示例:
@@ -298,15 +286,12 @@ i: 1
C++ 名称来自于递增运算符,暗示着“比 C 更进一步”。在早期的 Java 演讲中,*Bill Joy*(Java 作者之一)说“**Java = C++ --**”(C++ 减减),意味着 Java 在 C++ 的基础上减少了许多不必要的东西,因此语言更简单。随着进一步地学习,我们会发现 Java 的确有许多地方相对 C++ 来说更简便,但是在其他方面,难度并不会比 C++ 小多少。
-
## 关系运算符
-
关系运算符会通过产生一个布尔(**boolean**)结果来表示操作数之间的关系。如果关系为真,则结果为 **true**,如果关系为假,则结果为 **false**。关系运算符包括小于 `<`,大于 `>`,小于或等于 `<=`,大于或等于 `>=`,等于 `==` 和不等于 `!=`。`==` 和 `!=` 可用于所有基本类型,但其他运算符不能用于基本类型 **boolean**,因为布尔值只能表示 **true** 或 **false**,所以比较它们之间的“大于”或“小于”没有意义。
-
### 测试对象等价
关系运算符 `==` 和 `!=` 同样适用于所有对象之间的比较运算,但它们比较的内容却经常困扰 Java 的初学者。下面是代码示例:
@@ -330,7 +315,7 @@ true
false
```
-表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把47改成128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。
+表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把 47 改成 128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。
那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 `equals()` 方法,下面是如何使用 `equals()` 方法的示例:
@@ -381,7 +366,6 @@ false
大多数 Java 库类通过覆写 `equals()` 方法比较对象的内容而不是其引用。
-
## 逻辑运算符
每个逻辑运算符 `&&` (**AND**)、`||`(**OR**)和 `!`(**非**)根据参数的逻辑关系生成布尔值 `true` 或 `false`。下面的代码示例使用了关系运算符和逻辑运算符:
@@ -430,12 +414,11 @@ i != j is true
(i < 10) || (j < 10) is false
```
-在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、**OR**、**NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 "true" 或 "false" 字符串)。
+在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、 **OR**、 **NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 "true" 或 "false" 字符串)。
我们可以将前一个程序中 **int** 的定义替换为除 **boolean** 之外的任何其他基本数据类型。但请注意,**float** 类型的数值比较非常严格,只要两个数字的最小位不同则两个数仍然不相等;只要数字最小位是大于 0 的,那么它就不等于 0。
-
### 短路
逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。代码示例:
@@ -491,10 +474,8 @@ test1(0)&& test2(2)&& test3(2)
所以,运用“短路”可以节省部分不必要的运算,从而提高程序潜在的性能。
-
## 字面值常量
-
通常,当我们向程序中插入一个字面值常量(**Literal**)时,编译器会确切地识别它的类型。当类型不明确时,必须辅以字面值常量关联来帮助编译器识别。代码示例:
```java
@@ -563,7 +544,6 @@ bll: 101111101011111010111110101111
在文本值的后面添加字符可以让编译器识别该文本值的类型。对于 **Long** 型数值,结尾使用大写 `L` 或小写 `l` 皆可(不推荐使用 `l`,因为容易与阿拉伯数值 1 混淆)。大写 `F` 或小写 `f` 表示 **float** 浮点数。大写 `D` 或小写 `d` 表示 **double** 双精度。
-
十六进制(以 16 为基数),适用于所有整型数据类型,由前导 `0x` 或 `0X` 表示,后跟 0-9 或 a-f (大写或小写)。如果我们在初始化某个类型的数值时,赋值超出其范围,那么编译器会报错(不管值的数字形式如何)。在上例的代码中,**char**、**byte** 和 **short** 的值已经是最大了。如果超过这些值,编译器将自动转型为 **int**,并且提示我们需要声明强制转换(强制转换将在本章后面定义),意味着我们已越过该类型的范围界限。
八进制(以 8 为基数)由 0~7 之间的数字和前导零 `0` 表示。
@@ -573,13 +553,10 @@ Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,
使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。
-
### 下划线
-
Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 `_`,以使结果更清晰。这对于大数值的分组特别有用。代码示例:
-
```java
// operators/Underscores.java
public class Underscores {
@@ -604,7 +581,6 @@ public class Underscores {
7fe9b7aa
```
-
下面是合理使用的规则:
1. 仅限单 `_`,不能多条相连。
@@ -612,13 +588,12 @@ public class Underscores {
3. `F`、`D` 和 `L`的前后禁止出现 `_`。
4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。
-[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\r\n`。这种差异的包袱应该由编程语言来解决。这就是Java用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。
+[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\r\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。
-
### 指数计数法
- 指数总是采用一种我认为很不直观的记号方法:
+指数总是采用一种我认为很不直观的记号方法:
```java
// operators/Exponents.java
@@ -643,9 +618,9 @@ public class Exponents {
4.7E48
```
-在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 "1.39 x e-43",意味着“1.39 × 2.718 的-43次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。
+在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 "1.39 x e-43",意味着 “1.39 × 2.718 的 -43 次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10 的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。
-理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是“1.39 × 10的-43次方”。
+理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是 “1.39 × 10 的 -43 次方”。
注意如果编译器能够正确地识别类型,就不必使用后缀字符。对于下述语句:
@@ -662,10 +637,8 @@ float f4 = 1e-43f; //10 的幂数
编译器通常会将指数作为 **double** 类型来处理,所以假若没有这个后缀字符 `f`,编译器就会报错,提示我们应该将 **double** 型转换成 **float** 型。
-
## 位运算符
-
位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。
位运算源自 C 语言的底层操作。我们经常要直接操纵硬件,频繁设置硬件寄存器内的二进制位。Java 的设计初衷是电视机顶盒嵌入式开发,所以这种底层的操作仍被保留了下来。但是,你可能不会使用太多位运算。
@@ -676,16 +649,13 @@ float f4 = 1e-43f; //10 的幂数
我们将 **Boolean** 类型被视为“单位值”(one-bit value),所以它多少有些独特的地方。我们可以对 boolean 型变量执行与、或、异或运算,但不能执行非运算(大概是为了避免与逻辑“非”混淆)。对于布尔值,位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的位运算为我们新增了一个“异或”逻辑运算符,它并未包括在逻辑运算符的列表中。在移位表达式中,禁止使用布尔值,原因将在下面解释。
-
## 移位运算符
-
移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。
如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右侧的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。
-
移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:
```java
@@ -836,22 +806,19 @@ i >>> 5, int: 97591828, binary:
...
```
-
结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。数字的二进制表示称为有符号的两个补数。
-
## 三元运算符
三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 **if-else** 语句是不同的。下面是它的表达式格式:
-**布尔表达式 ? 值1 : 值2**
+**布尔表达式 ? 值 1 : 值 2**
-若表达式计算为 **true**,则返回结果 **值1** ;如果表达式的计算为 **false**,则返回结果 **值2**。
+若表达式计算为 **true**,则返回结果 **值 1** ;如果表达式的计算为 **false**,则返回结果 **值 2**。
当然,也可以换用普通的 **if-else** 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 **if-else** 不同的是,三元运算符是有返回结果的。请看下面的代码示例:
-
```java
// operators/TernaryIfElse.java
public class TernaryIfElse {
@@ -887,7 +854,6 @@ static int standardIfElse(int i) {
可以看出,`ternary()` 中的代码更简短。然而,**standardIfElse()** 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。
-
## 字符串运算符
@@ -927,10 +893,8 @@ x, y, z (summed) = 3
请注意主方法中的最后一个例子:我们经常会看到一个空字符串 `""` 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 **Integer.toString()**)。
-
## 常见陷阱
-
使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 `()`。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:
```java
@@ -945,7 +909,6 @@ while(x = y) {
在 C/C++ 里,类似的一个问题还有使用按位“与” `&` 和“或” `|` 运算,而非逻辑“与” `&&` 和“或” `||`。就象 `=` 和 `==` 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。
-
## 类型转换
@@ -972,7 +935,7 @@ public class Casting {
当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。
-除了布尔类型的数据, Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。
+除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。
### 截断和舍入
@@ -1037,20 +1000,16 @@ Math.round(fbelow): 0
### 类型提升
-
你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。
-
-## Java没有sizeof
-
+## Java 没有 sizeof
在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。
Java 不需要 ` sizeof()` 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。
-
## 运算符总结
上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 `//` 过滤了。代码示例:
@@ -1491,7 +1450,7 @@ public class Overflow {
输出结果:
-```java
+```text
big = 2147483647
bigger = -4
```
@@ -1500,7 +1459,6 @@ bigger = -4
对于 **char**,**byte** 或者 **short**,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 **boolean** 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。
-
## 本章小结
@@ -1508,8 +1466,7 @@ bigger = -4
[^1]: 我在 *Pomona College* 大学读过两年本科,在那里 47 被称之为“魔法数字”(*magic number*),详见 [维基百科](https://en.wikipedia.org/wiki/47_(number)) 。
-[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”
-
+[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”
-
+
\ No newline at end of file
From fd0cd9394fc41ba73842cf3cd7d32d81dc9ffffe Mon Sep 17 00:00:00 2001
From: Stanley Tam
Date: Sun, 18 Aug 2019 10:10:40 +0800
Subject: [PATCH 036/449] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E6=A0=87=E9=A2=98?=
=?UTF-8?q?=E7=A9=BA=E6=A0=BC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/00-Introduction.md | 4 ++--
docs/book/02-Installing-Java-and-the-Book-Examples.md | 4 ++--
docs/book/04-Operators.md | 4 ++--
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md
index 24646a9d..598f17ab 100644
--- a/docs/book/00-Introduction.md
+++ b/docs/book/00-Introduction.md
@@ -38,11 +38,11 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面对对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。
-## JDK 文档
+## JDK文档
甲骨文公司已经提供了免费的标准 JDK 文档。除非有必要,否则本书中将不再赘述 API 相关的使用细节。使用浏览器来即时搜索最新最全的 JDK 文档好过翻阅本书来查找。只有在需要补充特定的示例时,我才会提供有关的额外描述。
-## C 编程思想
+## C编程思想
*Thinking in C* 已经可以在 www.OnJava8.com 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。
diff --git a/docs/book/02-Installing-Java-and-the-Book-Examples.md b/docs/book/02-Installing-Java-and-the-Book-Examples.md
index b9633d66..86f76082 100644
--- a/docs/book/02-Installing-Java-and-the-Book-Examples.md
+++ b/docs/book/02-Installing-Java-and-the-Book-Examples.md
@@ -1,6 +1,6 @@
[TOC]
-# 第二章 安装 Java 和本书用例
+# 第二章 安装Java和本书用例
现在,我们来为这次阅读之旅做些准备吧!
@@ -89,7 +89,7 @@ Linux: 在 home 目录打开 Shell。
```
-## Java 安装
+## Java安装
为了编译和运行代码示例,首先你必须安装 JDK(Java Development Kit,JAVA 软件开发工具包)。本书中采用的是 JDK 8。
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index 6da34a35..307cd258 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -10,7 +10,7 @@ Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于
如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。
-## 使用 Java 运算符
+## 使用Java运算符
运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
@@ -1003,7 +1003,7 @@ Math.round(fbelow): 0
你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。
-## Java 没有 sizeof
+## Java没有sizeof
在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。
From 4c6f47d2f98ab6f57f13484485169397ed892f83 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Sun, 18 Aug 2019 18:13:50 +0800
Subject: [PATCH 037/449] Update 03-Objects-Everywhere.md
---
docs/book/03-Objects-Everywhere.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md
index 8884e234..8829e427 100644
--- a/docs/book/03-Objects-Everywhere.md
+++ b/docs/book/03-Objects-Everywhere.md
@@ -357,7 +357,7 @@ void nothing2() {
在看到第一个 Java 程序之前,我们还必须理解其他几个问题。
-#### 命名可见性
+### 命名可见性
命名控制在任何一门编程语言中都是一个问题。如果你在两个模块中使用相同的命名,那么如何区分这两个名称,并防止两个名称发生“冲突”呢?在 C 语言编程中这是很具有挑战性的,因为程序通常是一个无法管理的名称海洋。C++ 将函数嵌套在类中,所以它们不会和嵌套在其他类中的函数名冲突。然而,C++ 还是允许全局数据和全局函数,因此仍有可能发生冲突。为了解决这个问题,C++ 使用附加的关键字引入了*命名空间*。
@@ -374,7 +374,7 @@ Java 采取了一种新的方法避免了以上这些问题:为一个类库生
对于这本书中的例子,我不想让深层次结构给你的学习带来额外的麻烦,这实际上需要你在开始之前学习熟悉一种重量级的 IDE。所以,我们的每个章节的示例都位于一个浅的子目录中,以章节标题为名。这导致我偶尔会与遵循深层次方法的工具发生冲突。
-#### 使用其他组件
+### 使用其他组件
无论何时在程序中使用预先定义好的类,编译器都必须找到该类。最简单的情况下,该类存在于被调用的源代码文件中。此时我们使用该类 —— 即使该类在文件的后面才会被定义(Java 消除了所谓的“前向引用”问题)。而如果一个类位于其他文件中,又会怎样呢?你可能认为编译器应该足够智能去找到它,但这样是有问题的。想象一下,假如你要使用某个类,但目录中存在多个同名的类(可能用途不同)。或者更糟糕的是,假设你正在编写程序,在构建过程中,你想将某个新类添加到类库中,但却与已有的类名称冲突。
@@ -393,7 +393,7 @@ import java.util.*;
本书中的示例很小,为简单起见,我们通常会使用 `.*` 形式略过导入。然而,许多教程书籍都会要求程序员逐一导入每个类。
-#### static关键字
+### static关键字
类是对象的外观及行为方式的描述。通常只有在使用 `new` 创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。
@@ -619,4 +619,4 @@ class AllTheColorsOfTheRainbow {
[^10]: 为了保持本书的代码排版紧凑,我并没完全遵守规范,但我尽量会做到符合 Java 标准。
-
\ No newline at end of file
+
From b5dfc6069e8822b8afb088732fb40be111958679 Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Mon, 19 Aug 2019 22:08:39 +0800
Subject: [PATCH 038/449] =?UTF-8?q?revision[13]=2013=E7=AB=A0=E7=BB=93?=
=?UTF-8?q?=E6=9D=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/13-Functional-Programming.md | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index fc6af24f..cf7b1920 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -1274,7 +1274,7 @@ public class Closure9 {
### 作为闭包的内部类
-我们可以复制我们的例子使用匿名内部类:
+我们可以使用匿名内部类重写之前的例子:
```java
// functional/AnonymousClosure.java
@@ -1300,7 +1300,7 @@ public class AnonymousClosure {
## 函数组合
-函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,有一个使用 `andThen()` 的函数组合示例。在 `java.util.function` 包里包含支持函数组合的一些接口方法 [^7]。
+函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,有一个使用 `andThen()` 的函数组合示例。一些 `java.util.function` 接口中包含支持函数组合的方法 [^7]。
| 组合方法 | 支持接口 |
| :----- | :----- |
@@ -1343,7 +1343,7 @@ _fter _ll _mbul_nces
这里我们重点看正在创建的新函数 `f4`。它调用 `apply()` 的方式与常规几乎无异[^8]。
-当 `f1` 获得字符串时,它已经被`f2` 剥离了前三个字符。这是因为对 `compose(f2)`的调用意味着在 `f1` 之前调用 `f2`。
+当 `f1` 获得字符串时,它已经被`f2` 剥离了前三个字符。这是因为 `compose(f2)` 表示 `f2` 的调用发生在 `f1` 之前。
下例是 `Predicate` 的逻辑运算演示.代码示例:
@@ -1376,14 +1376,13 @@ foobaz
`p4` 获取到了所有断言并组合成一个更复杂的断言。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。
-正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建一个流的字符串对象集合,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的断言来确定对象的去留。最后我们使用 `forEach()` 和 `println` 方法打印出留下来的对象。
+正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的断言来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。
从输出结果我们可以看到 `p4` 的工作流程:任何带有 `foo` 的东西都会留下,即使它的长度大于 5。 `fongopuckey` 因长度超出和不包含 `bar` 而被丢弃。
## 柯里化和部分求值
-
[柯里化](https://en.wikipedia.org/wiki/Currying)(Currying)的名称来自于其发明者之一 *Haskell Curry*。他可能是计算机领域唯一名字被命名重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。
```java
@@ -1458,7 +1457,7 @@ public class Curry3Args {
Hi Ho Hup
```
-对于每个级别的箭头级联(Arrow-cascading),你可以围绕类型声明包装成另一个函数。
+对于每个级别的箭头级联(Arrow-cascading),你在类型声明中包裹了另一个 **Function**。
处理基本类型和装箱时,请使用适当的 **Function** 接口:
@@ -1473,7 +1472,7 @@ public class CurriedIntAdd {
curriedIntAdd = a -> b -> a + b;
IntUnaryOperator add4 = curriedIntAdd.apply(4);
System.out.println(add4.applyAsInt(5));
- }
+ }
}
```
@@ -1483,13 +1482,13 @@ public class CurriedIntAdd {
9
```
-更多柯里化示例,可搜索互联网。通常这些是 Java 以外的语言,理解它们的基本概念,就应该很容易翻译。
+可以在互联网上找到更多的柯里化示例。通常它们是用 Java 之外的语言实现的,但如果理解了柯里化的基本概念,你可以很轻松地用 Java 实现它们。
## 纯函数式编程
-即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。
+即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。
这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些规则) 或 Clojure (需要的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。
@@ -1509,7 +1508,7 @@ Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而
[^1]: 功能粘贴在一起的方法的确有点与众不同,但它仍不失为一个库。
[^2]: 例如,这个电子书是利用 [Pandoc](http://pandoc.org/) 制作出来的,它是用纯函数式语言 [Haskell](https://www.haskell.org/) 编写的一个程序 。
-[^3]: 有时函数式语言将其描述为“代码即数据”。“
+[^3]: 有时函数式语言将其描述为“代码即数据”。
[^4]: 这个语法来自 C++。
[^5]: 我还没有验证过这种说法。
[^6]: 当你理解了[并发编程](./24-Concurrent-Programming.md)章节的内容,你就能明白为什么更改共享变量 “不是线程安全的” 的了。
From 03945f59b26814221654a394e799b861c0832ca4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
Date: Wed, 21 Aug 2019 14:54:10 +0800
Subject: [PATCH 039/449] =?UTF-8?q?=E5=A4=9A=E5=87=BA=E4=B8=80=E4=B8=AA?=
=?UTF-8?q?=E5=AD=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
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 9f5676e8..dabc664e 100644
--- a/docs/book/05-Control-Flow.md
+++ b/docs/book/05-Control-Flow.md
@@ -444,7 +444,7 @@ public class BreakAndContinue {
## 臭名昭著的 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)。
+[**goto** 关键字](https://en.wikipedia.org/wiki/Goto) 很早就在程序设计语言中出现。事实上,**goto** 起源于[汇编](https://en.wikipedia.org/wiki/Assembly_language)(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。
一个源码级别跳转的 **goto**,为何招致名誉扫地呢?若程序总是从一处跳转到另一处,还有什么办法能识别代码的控制流程呢?随着 *Edsger Dijkstra*发表著名的 “Goto 有害” 论(*Goto considered harmful*)以后,**goto** 便从此失宠。甚至有人建议将它从关键字中剔除。
From 29f5d0a1af0a5dad3f368961e1d32d24d8e806aa Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Wed, 21 Aug 2019 22:58:31 +0800
Subject: [PATCH 040/449] revision[14] Ranges of int
---
docs/book/14-Streams.md | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 24db4c68..dd4387ac 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -73,12 +73,12 @@ public class ImperativeRandoms {
**注意**,你必须要研究程序的真正意图,而在 `Randoms.java` 中,代码只是告诉了你它正在做什么。这种语义清晰性也是 Java 8 的流式编程更受推崇的重要原因。
-在 `ImperativeRandoms.java` 中显式地编写迭代机制称之为外部迭代。而在 `Randoms.java` 中,流式编程采用内部迭代。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。在[并发编程](24-Concurrent-Programming.md)这一章了解这一点。
+在 `ImperativeRandoms.java` 中显式地编写迭代机制称为外部迭代。而在 `Randoms.java` 中,流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。
另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
-
+
## 流支持
Java 设计者面临着这样一个难题:现存的大量类库不仅为 Java 所用,同时也被应用在整个 Java 生态圈数百万行的代码中。如何将一个全新的流的概念融入到现有类库中呢?
@@ -94,7 +94,7 @@ Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `def
## 流创建
-你可以通过 `Stream.of()` 很容易地将一组元素转化成为流(`Bubble` 类在之前的章节中已经定义过了):
+你可以通过 `Stream.of()` 很容易地将一组元素转化成为流(`Bubble` 类在本章的后面定义):
```java
// streams/StreamOf.java
@@ -164,11 +164,11 @@ e: 2.718
pi: 3.14159
```
-在创建 `List` 对象之后,我们只需要简单地调用所有集合中都有的 `stream()`。中间操作 `map()` 会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到流中。通常 `map()` 会获取对象并产生新的对象,但是这里有特殊版本的方法用于数值类型的流。例如,`mapToInt()` 方法将一个对象流(objects stream)转换成为包含整形数字的 `IntStream`。同样,针对 `Float` 和 `Double` 也有类似名字的操作。
+在创建 `List` 对象之后,我们只需要简单地调用所有集合中都有的 `stream()`。中间操作 `map()` 会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到后续的流中。通常 `map()` 会获取对象并产生新的对象,但在这里产生了特殊的用于数值类型的流。例如,`mapToInt()` 方法将一个对象流(object stream)转换成为包含整型数字的 `IntStream`。同样,针对 `Float` 和 `Double` 也有类似名字的操作。
我们通过调用字符串的 `split()`(该方法会根据参数来拆分字符串)来获取元素用于定义变量 `w`。稍后你会知道 `split()` 参数可以是十分复杂,但在这里我们只是根据空格来分割字符串。
-为了从 **Map** 集合中产生流数据,我们首先调用 `entrySet()` 去产生一个对象流,每个对象都包含一个 `key` 键以及与其相关联的 `value` 值。然后调用 `getKey()` 和 `getValue()` 将其分开。
+为了从 **Map** 集合中产生流数据,我们首先调用 `entrySet()` 产生一个对象流,每个对象都包含一个 `key` 键以及与其相关联的 `value` 值。然后分别调用 `getKey()` 和 `getValue()` 获取值。
### 随机数流
@@ -265,7 +265,7 @@ public class RandomGenerators {
为了消除冗余代码,我创建了一个泛型方法 `show(Stream stream)` (在讲解泛型之前就使用这个特性,确实有点作弊,但是回报是值得的)。类型参数 `T` 可以是任何类型,所以这个方法对 **Integer**、**Long** 和 **Double** 类型都生效。但是 **Random** 类只能生成基本类型 **int**, **long**, **double** 的流。幸运的是, `boxed()` 流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得 `show()` 能够接受流。
-我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个从文本文件提供字符串对象的例子。
+我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个文本文件提供字符串对象的例子。
Cheese.dat 文件内容:
@@ -299,7 +299,7 @@ public class RandomWords implements Supplier {
}
}
public String get() {
- return words.get(rand.nextint(words.size()));
+ return words.get(rand.nextInt(words.size()));
}
@Override
public String toString() {
@@ -323,18 +323,18 @@ it shop sir the much cheese by conclusion district is
在这里你可以看到更为复杂的 `split()` 运用。在构造器中,每一行都被 `split()` 通过空格或者被方括号包裹的任意标点符号进行分割。在结束方括号后面的 `+` 代表 `+` 前面的东西可以出现一次或者多次。
-我们注意到在构造函数中循环体使用命令式编程(外部迭代)。在以后的例子中,你会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕,但使用流会让你的代码更好看一些。
+我们注意到在构造函数中循环体使用命令式编程(外部迭代)。在以后的例子中,你甚至会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。
在 `toString()` 和主方法中你看到了 `collect()` 收集操作,它根据参数来组合所有流中的元素。
-当你使用 **Collectors.**`joining()`,你将会得到一个 `String` 类型的结果,每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于获取不同的结果。
+当你使用 **Collectors.**`joining()`,你将会得到一个 `String` 类型的结果,每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于产生不同的结果。
-在主方法中,我们看到了 **Stream.**`generate()` 的预览版本,它可以把任意 `Supplier` 用于生成 `T` 类型的流。
+在主方法中,我们提前看到了 **Stream.**`generate()` 的用法,它可以把任意 `Supplier` 用于生成 `T` 类型的流。
### int 类型的范围
-`IntStream` 类提供了 `range()` 方法用于生成整数序列的流。编写循环时,这个方法会更加便利:
+`IntStream` 类提供了 `range()` 方法用于生成整型序列的流。编写循环时,这个方法会更加便利:
```java
// streams/Ranges.java
From 9266ecf74d26b8c34a68309fd998a8af8ef47214 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
Date: Fri, 23 Aug 2019 16:13:36 +0800
Subject: [PATCH 041/449] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=EF=BC=8C=E4=BB=A5=E5=8F=8A=E5=BB=BA=E8=AE=AE?=
=?UTF-8?q?=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
In C++, a constructor that takes no arguments is called the default
constructor. This term was in use for many years before Java
appeared, but for some reason the Java designers decided to use the
term no-arg constructor, which I found very awkward and
unnecessary, so I resisted and attempted to continue using “default
constructor.” Java 8 has introduced the use of default as a keyword
for method definitions, so I must relent, choke down a bit of bile, and
use no-arg.
Like any method,
建议修改:
根据第六章作者认为在本书更适合用无参构造器来命名,就像一个没有参数的方法一样,而且第六章的小标题也是 无参构造器。
---
docs/book/15-Exceptions.md | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md
index 16aba80a..02a00e75 100644
--- a/docs/book/15-Exceptions.md
+++ b/docs/book/15-Exceptions.md
@@ -15,7 +15,7 @@ Java 使用异常来提供一致的错误报告模型,使得构件能够与客
Java 中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更加确信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。
-因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理 3] 程序,并将展示当方法出问题的时候,如何产生自定义的异常。
+因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理程序,并将展示当方法出问题的时候,如何产生自定义的异常。
@@ -35,7 +35,7 @@ C 以及其他早期语言常常具有多种错误处理模式,这些模式往
-异常情形(exceptional condition)是指阻止当前方法或作用城继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
+异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
除法就是一个简单的例子。除数有可能为 0,所以先进行检查很有必要。但除数为 0 代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为 0 的情况。但如果这是一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。
@@ -56,7 +56,7 @@ if(t == null)
### 异常参数
-与使用 Java 中的其他对象一样,我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
+与使用 Java 中的其他对象一样,我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
```java
throw new NullPointerException("t = null");
@@ -82,7 +82,7 @@ try {
}
```
-对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 ry 块里,然后只需在一个地方就可以捕获所有异常。这意味着代码将更容易编写和阅读,因为完成任务的代码没有与错误检查的代码混在一起。
+对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读,因为代码的目标是弄清楚错误检查。
### 异常处理程序
@@ -121,7 +121,7 @@ try {
不必拘泥于 Java 中已有的异常类型。Java 提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。
-要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生默认构造器,所以这几乎不用写多少代码:
+要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生无参构造器,所以这几乎不用写多少代码:
```java
// exceptions/InheritingExceptions.java
@@ -153,7 +153,7 @@ Throw SimpleException from f()
Caught it!
```
-编译器创建了默认构造器,它将自动调用基类的默认构造器。本例中不会得到像 SimpleException(String) 这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。
+编译器创建了无参构造器,它将自动调用基类的无参构造器。本例中不会得到像 SimpleException(String) 这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。
本例的结果被打印到了控制台上,本书的输出显示系统正是在控制台上自动地捕获和测试这些结果的。但是,你也许想通过写入 System.err 而将错误发送给标准错误流。通常这比把错误信息输出到 System.out 要好,因为 System.out 也许会被重定向。如果把结果送到 System.err,它就不会随 System.out 一起被重定向,这样更容易被用户注意。
@@ -407,7 +407,7 @@ void f() { // ...
这种在编译时被强制检查的异常称为被检查的异常。
-## 任意所有捕获
+## 捕获所有异常
可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类 Exception,就可以做到这一点(事实上还有其他的基类,但 Exception 是所有编程行为相关的基类):
@@ -932,9 +932,9 @@ if(t == null)
throw new NullPointerException();
```
-如果必须对传递给方法的每个引用都检查其是否为 nul(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。
+如果必须对传递给方法的每个引用都检查其是否为 null(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。
-属于运行时异常的类型有很多,它们会自动被 lava 虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。
+属于运行时异常的类型有很多,它们会自动被 java 虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。
RuntimeException 代表的是编程错误:
@@ -1294,7 +1294,7 @@ public class ExceptionSilencer {
## 异常限制
-当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味若,当基类使用的代码应用到其派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。
+当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,若当基类使用的代码应用到其派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。
下面例子演示了这种(在编译时)施加在异常上面的限制:
@@ -1951,10 +1951,10 @@ try {
## 其他可选方式
-|异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”
+异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”
发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽格。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。
-异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样以来,主于代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋向于减少。
+异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样以来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋向于减少。
“被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句,这就导致了吞食则有害(harmful if swallowed)的问题:
@@ -2166,7 +2166,7 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型
异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。
-异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在.Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过语如 C++这类语言的长处之一,因为在 C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
+异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过语如 C++这类语言的长处之一,因为在 C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。
From f95fc7ea54ac4df3e844451eb000d34d29e12bd6 Mon Sep 17 00:00:00 2001
From: LingCoder <34231795+LingCoder@users.noreply.github.com>
Date: Fri, 23 Aug 2019 19:15:28 +0800
Subject: [PATCH 042/449] Update 15-Exceptions.md
---
docs/book/15-Exceptions.md | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md
index 02a00e75..6d17b601 100644
--- a/docs/book/15-Exceptions.md
+++ b/docs/book/15-Exceptions.md
@@ -1968,7 +1968,7 @@ try {
当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第 2 版中,我在处理程序里通过打印栈轨迹的方法“修补”了这个问题(本章中的很多例子还是使用了这种方法,看起来还是比较合适的),虽然这样可以跟踪异常的行为,但是仍旧不知道该如何处理异常。这一节,我们来研究一下“被检查的异常”及其并发症,以及采用什么方法来解决这些问题。
-这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。
+这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。
### 历史
@@ -1990,15 +1990,15 @@ C++从 CLU 那里还带来另一种思想:异常说明。这样,就可以用
C++的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++会调用标准类库的 unexpected() 函数。
-值得注意的是,由于使用了模板,C++的标准类库实现里根本没有使用异常说明。在 Java 中,对于范型用于异常说明的方式存在着一些限制。
+值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于范型用于异常说明的方式存在着一些限制。
### 观点
-首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++异常说明的启发,以及受 C++程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。
+首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。
其次,仅从示意性的例子和小程序来看,“被检查的异常”的好处很明显。但是当程序开始变大的时候,就会带来一些微妙的问题。当然,程序不是一下就变大的,这有个过程。如果把不适用于大项目的语言用于小项目,当这些项目不断膨胀时,突然有一天你会发现,原来可以管理的东西,现在已经变得无法管理了。这就是我所说的过多的类型检查,特别是“被检查的异常"所造成的问题。
-看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C#的设计人员发现:
+看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C# 的设计人员发现:
> “仅从小程序来看,会认为异常说明能增加开发人员的效率,并提高代码的质量;但考察大项目的时候,结论就不同了-开发效率下降了,而代码质量只有微不足道的提高,甚至毫无提高”。
@@ -2053,7 +2053,7 @@ public class MainException {
### 把“被检查的异常”转换为“不检查的异常”
-在编写你自己使用的简单程序时,从 main() 中抛出异常是很方便的,但这不是通用的方法。
+在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的,但这不是通用的方法。
问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或若打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样:
@@ -2166,7 +2166,7 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型
异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。
-异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过语如 C++这类语言的长处之一,因为在 C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
+异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过语如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。
@@ -2180,11 +2180,11 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型
我们开始讨论Go编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。
-我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做Big Upfront Design。
+我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。
他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。
-替代方案非常简单,起初它几乎看起来像C一样。因为Go从一开始就包含了元组,所以你可以轻松地从函数调用中返回两个对象:
+替代方案非常简单,起初它几乎看起来像 C 一样。因为 Go 从一开始就包含了元组,所以你可以轻松地从函数调用中返回两个对象:
```go
result, err := functionCall()
@@ -2194,11 +2194,11 @@ result, err := functionCall()
就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。
-起初这似乎很原始,是古代的回归。但到目前为止,我发现Go中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?
+起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?
-它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进catch和finally子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。
+它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。
-James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过try-finally跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。
+James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。
关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。
From b4088e96ec66a946272dd35a2aee84e21d0069a4 Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Sun, 25 Aug 2019 14:13:13 +0800
Subject: [PATCH 043/449] =?UTF-8?q?[ISSUE=20#40]=E6=A0=A1=E8=AE=A2?=
=?UTF-8?q?=E6=B5=81=E5=BC=8F=E7=BC=96=E7=A8=8B-=E4=B8=AD=E9=97=B4?=
=?UTF-8?q?=E6=93=8D=E4=BD=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 58 ++++++++++++++++++++---------------------
1 file changed, 29 insertions(+), 29 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index dd4387ac..8b9835c8 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -607,7 +607,7 @@ Not much of a cheese shop really
**注意**,构造器会添加文件中的所有单词(除了第一行,它是包含文件路径信息的注释),但是其并没有调用 `build()`。只要你不调用 `stream()` 方法,就可以继续向 `builder` 对象中添加单词。
-在该类的更完整版本中,你可以添加一个标志位用于查看 `build()` 是否被调用,并且可能的话增加一个可以添加更多单词的方法。在 `Stream.Builder` 调用 `build()` 方法后继续尝试添加单词会产生一个异常。
+在该类的更完整形式中,你可以添加一个标志位用于查看 `build()` 是否被调用,并且可能的话增加一个可以添加更多单词的方法。在 `Stream.Builder` 调用 `build()` 方法后继续尝试添加单词会产生一个异常。
### Arrays
@@ -796,9 +796,9 @@ you what to the that sir leads in district And
### 移除元素
-* `distinct()`:在 `Randoms.java` 中 `distinct()` 用于去除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。
+* `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。
-* `filter(Predicate)`:过滤操作只会保留那些与传递进去的过滤器函数计算结果为 `true` 元素。
+* `filter(Predicate)`:过滤操作会保留与传递进去的过滤器函数计算结果为 `true` 元素。
在下例中,`isPrime()` 作为过滤器函数,用于检测质数。
@@ -835,7 +835,7 @@ public class Prime {
467 479 487 491 499 503 509 521 523 541
```
-`rangeClosed()` 包含了上限值。如果不能整除,即余数不等于 0,则 `noneMatch()` 操作返回 ture,如果出现任何等于 0 的则返回 `false`。 `noneMatch()` 操作在首次失败之后就会退出,而不是尝试匹配所有。
+`rangeClosed()` 包含了上限值。如果不能整除,即余数不等于 0,则 `noneMatch()` 操作返回 `true`,如果出现任何等于 0 的结果则返回 `false`。 `noneMatch()` 操作一旦有失败就会退出。
### 应用函数到元素
@@ -906,7 +906,7 @@ class FunctionMap {
5
```
-在“Increment”的测试中,我们使用 **Integer.**`parseInt()` 去试图将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出一个 **NumberFormatException** 异常,我们只需回过头来将原始字符串放回到输出流中。
+在上面的自增示例中,我们使用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出 **NumberFormatException** 异常,我们只须回过头来将原始字符串放回到输出流中。
在以上例子中,`map()` 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例:
@@ -979,23 +979,23 @@ class FunctionMap3 {
17.000000 1.900000 0.230000
```
-不幸的是,Java 设计者并没有尽最大努力来消除基本类型。
+遗憾的是,Java 设计者并没有尽最大努力去消除基本类型。
-### 在 `map()` 期间组合流
+### 在 `map()` 中组合流
-假设你有一个传入的元素流,并且你打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。你想要的只是产生流的元素,但是你生成的是一个元素流的流。
+假设我们现在有了一个传入的元素流,并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要的只是产生流的元素,生成的却是一个元素流的流。
-`flatMap()` 做了两件事情:它获取你的流产生( stream-producing)函数,并将其应用于新到的元素(正如 `map()` 所做的),然后获取每一个流并将其“展平”为元素。所以它的输出只是元素。
+`flatMap()` 在这里做了两件事情:获取流产生( stream-producing)函数,并将其应用于新到的元素(如 `map()` 所做的),然后获取每一个流并将其“扁平”为元素。所以它的输出只是元素。
`flatMap(Function)`:当 `Function` 产生流时使用。
-`flatMapToInt(Function)`:当 `Function` 产生 `IntStream` 时使用。
+`flatMapToInt(Function)`:当 `Function` 产生 `IntStream` 时使用。
`flatMapToLong(Function)`:当 `Function` 产生 `LongStream` 时使用。
`flatMapToDouble(Function)`:当 `Function` 产生 `DoubleStream` 时使用。
-为了了解它是如何工作的,我们将从 `map()` 的一个刻意设计的函数开始,这个函数接受一个整数并产生一个字符串流:
+为了弄清它的工作原理,我们从 `map()` 的一个刻意设计的函数开。该函数接受一个整数并产生一个字符串流:
```java
// streams/StreamOfStreams.java
@@ -1018,7 +1018,7 @@ java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
```
-我们天真的希望能够得到字符串流,但是我们得到的却是流元素为“Head”流的流。但是我们可以使用 `flatMap()` 来轻松解决这个问题:
+我们天真的希望能够得到字符串流,但是结果却是流元素为“Head”流的流。我们可以使用 `flatMap()` 解决这个问题:
```java
// streams/FlatMap.java
@@ -1046,9 +1046,9 @@ Fozzie
Beaker
```
-因此,从映射返回的每个流都会自动展平为其组件字符串。
+从映射返回的每个流都会自动扁平为其组件字符串。
-如下是另一演示,我们从一个整数流开始,然后使用每一个整数去创建许多随机数。
+如下是另一演示,我们从一个整数流开始,使用每一个整数去创建更多的随机数。
```java
// streams/StreamOfRandoms.java
@@ -1071,13 +1071,13 @@ public class StreamOfRandoms {
58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1
```
-我在这里引入了`concat()`,它以参数顺序组合了两个流。 因此,在每个随机 `Integer` 流的末尾,我添加一个 -1 作为标记,因此你可以看到最终流确实是从一组展平流中创建的。
+在这里我们引入了 `concat()`,它以参数顺序组合了两个流。 如此,我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。
-因为 `rand.ints()` 产生了一个 `IntStream`,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数版本。
+因为 `rand.ints()` 产生的是一个 `IntStream`,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数形式。
-让我们再看一下将文件划分为单词流的任务。我们上一次遇到的是 **FileToWordsRegexp.java**,它的问题是它需要我们将整个文件读入行列表中 —— 因此我们需要存储该列表。我们真正想要的是创建一个不需要中间存储的单词流。
+让我们再看一下将文件划分为单词流的任务。我们最后使用到的是 **FileToWordsRegexp.java**,它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。
-再一次,我们使用 ` flatMap()` 来解决这个问题:
+下面,我们再使用 ` flatMap()` 来解决这个问题:
```java
// streams/FileToWords.java
@@ -1089,22 +1089,22 @@ public class FileToWords {
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
- Pattern.compile("\W+").splitAsStream(line));
+ Pattern.compile("\\W+").splitAsStream(line));
}
}
```
-因为 `stream()` 方法可以自己完成整个创建流的构成,所以它现在是个静态方法。
+`stream()` 现在是一个静态方法,因为它可以自己完成整个流创建过程。
-注意 **\\\\W+** 是一个正则表达式。**\\\\W** 的意思是 “非单词字符”,`+` 的意思是“可以出现一次或者多次”。小写版本的 “**\\\\w**” 代表“单词字符”。
+**注意**:`\\W+` 是一个正则表达式。他表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\w` 表示“单词字符”。
-我们之前遇到的问题是 `Pattern.compile().splitAsStream()`产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流展平为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:
+我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:
```java
.flatMap(line -> Arrays.stream(line.split("\\W+"))))
```
-因为我们拥有了一个真的流(而不是在 `FileToWordsRegexp.java` 基于集合存储的流),每一次我们想要一个新的流就必须从头创建,因为这个流并不能被重复使用:
+有了真正的、而非 `FileToWordsRegexp.java` 中基于集合存储的流,我们每次使用都必须从头创建,因为流并不能被复用:
```java
// streams/FileToWordsTest.java
@@ -1129,7 +1129,7 @@ public class FileToWordsTest {
Not much of a cheese shop really
```
-在 `System.out.format()` 中的 `%s` 表明参数为 String 类型。
+在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。
@@ -1144,7 +1144,7 @@ Not much of a cheese shop really
- `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**
- `max` 和 `min()` 返回一个包含最大值或者最小值的 **Optional** 对象,如果流为空则返回 **Optional.empty**
- 不再以 “identity”对象开头版本的 `reduce()` 将其返回值包装在 **Optional** 中。(“identity”对象成为另一个版本的 `reduce()` 的默认结果,因此不存在空结果的风险)
+ 不再以 “identity”对象开头形式的 `reduce()` 将其返回值包装在 **Optional** 中。(“identity”对象成为另一个形式的 `reduce()` 的默认结果,因此不存在空结果的风险)
对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。
@@ -1297,7 +1297,7 @@ Epithets
Caught java.lang.Exception: Supplied
```
-` test() ` 方法通过使用与所有示例方法匹配的 **Consumer** 来防止代码重复。`orElseThrow()` 使用 **catch** 关键字来捕获 `orElseThrow()` 抛出的异常。你将会在 [异常]() 这一章节中学习细节。
+` test()` 通过使用与所有示例方法匹配的 **Consumer** 来防止代码重复。`orElseThrow()` 使用 **catch** 关键字来捕获 `orElseThrow()` 抛出的异常。你将会在 [异常]() 这一章节中学习细节。
### 创建 Optional(Creating Optionals)
@@ -1702,7 +1702,7 @@ public class RandInts {
### 对每个元素应用最终操作(Apply a Final Operation to Every Element)
- `forEach(Consumer)`:你已经看到很多次 `System.out::println` 作为 **Consumer** 函数。
-- `forEachOrdered(Consumer)`: 这个版本保证了 forEach 的操作顺序是原始流顺序。
+- `forEachOrdered(Consumer)`: 这个形式保证了 forEach 的操作顺序是原始流顺序。
第一种形式是显式地设计为以任何顺序操作元素,这只在引入 `parallel()` 操作时才有意义。在 [并发]() 章节之前我们不会深入研究这个问题,但是这里有一个简单的介绍:`parallel()` 告诉 Java 尝试在多个处理器上运行操作。它可以做到这一点是因为我们使用流——流可以被分割为多个流(通常一个流一个处理器)并且每个流运行在不同的处理器上。因为我们使用的是内部循环而不是外部循环,这是可行的。
@@ -1784,7 +1784,7 @@ stream, streams, throws, toCollection, trim, util,
void, words2]
```
-`Files.lines()` 打开 **Path** 并将其转换成为行流。下一行代码将根据一个或者多个非单词字符(\\\\w+)作为边界对行进行分割,然后使用 `Arrays.stream()` 将其转化成为流,并将结果展平映射成为单词流。`matches(\\d+)` 寻找并移除那些全是数字的字符串(注意 **words2** 是通过的)。接下来我们使用 `String.trim()` 去除单词两边的空白,`filter()`过滤所有长度小于 3 的单词,然后只获取 100 个单词,并最终将其塞入到 **TreeSet** 中。
+`Files.lines()` 打开 **Path** 并将其转换成为行流。下一行代码将根据一个或者多个非单词字符(\\\\w+)作为边界对行进行分割,然后使用 `Arrays.stream()` 将其转化成为流,并将结果扁平映射成为单词流。`matches(\\d+)` 寻找并移除那些全是数字的字符串(注意 **words2** 是通过的)。接下来我们使用 `String.trim()` 去除单词两边的空白,`filter()`过滤所有长度小于 3 的单词,然后只获取 100 个单词,并最终将其塞入到 **TreeSet** 中。
我们也可以在流中生成 **Map**:
@@ -1872,7 +1872,7 @@ cheese
- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。
- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。
-- `reduce(identity, BiFunction, BinaryOperator)`:这个版本更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显示的组合 `map()` 和 `reduce()` 来更简单的表达这一点。
+- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显示的组合 `map()` 和 `reduce()` 来更简单的表达这一点。
如下是一个用于演示 `reduce()` 的示例:
From 304a000f9554a4e17b426352917c7245c78e8074 Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Sun, 25 Aug 2019 21:53:03 +0800
Subject: [PATCH 044/449] =?UTF-8?q?[ISSUE=20#40]=E6=A0=A1=E8=AE=A2?=
=?UTF-8?q?=E6=B5=81=E5=BC=8F=E7=BC=96=E7=A8=8B-Optional=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 71 +++++++++++++++++++++++------------------
1 file changed, 40 insertions(+), 31 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 8b9835c8..6924bd4b 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -1136,15 +1136,15 @@ Not much of a cheese shop really
## Optional类
-在我们查看终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流为空时会被中断。在流中放置 `null` 是很好的中断方法。我们可以使用哪种对象作为流元素的持有者,如果我们寻找的元素并不存在也可以友好的告诉我们(也就是说,没有异常)?
+在我们查看终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流为空时会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好的提示我们(也就是说,没有异常)?
-这个想法是通过 **Optional** 实现的。确保标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。它们包括:
+**Optional** 可以实现这样的功能。首先确保准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。它们包括:
- `findFirst()` 返回一个包含第一个元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**
- `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**
-- `max` 和 `min()` 返回一个包含最大值或者最小值的 **Optional** 对象,如果流为空则返回 **Optional.empty**
+- `max()` 和 `min()` 返回一个包含最大值或者最小值的 **Optional** 对象,如果流为空则返回 **Optional.empty**
- 不再以 “identity”对象开头形式的 `reduce()` 将其返回值包装在 **Optional** 中。(“identity”对象成为另一个形式的 `reduce()` 的默认结果,因此不存在空结果的风险)
+ `reduce()` 不再以 `identity` 形式开头,而是将其返回值包装在 **Optional** 中。(`identity` 对象成为其他形式的 `reduce()` 的默认结果,因此不存在空结果的风险)
对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。
@@ -1193,7 +1193,7 @@ Stream s = Stream.empty();
就可以在调用 `empty()` 时推断类型。
-这个示例展现了 **Optional** 的两个基本用法:
+这个示例展示了 **Optional** 的两个基本用法:
```java
// streams/OptionalBasics.java
@@ -1219,11 +1219,12 @@ Epithets
Nothing inside!
```
-当你接收到 **Optional** 对象时,你首先调用 `isPresent()` 检查其中是否包含元素。如果存在,你可以使用 `get()` 获取。
+当你接收到 **Optional** 对象时,应首先调用 `isPresent()` 检查其中是否包含元素。如果存在,可使用 `get()` 获取。
-### 便利函数(Convenience Functions)
+
+### 便利函数
-有许多便利函数可以解包 **Optional** ,简化了“检查并对所包含的对象执行某些操作”的上述过程:
+有许多便利函数可以解包 **Optional** ,这简化了上述“对所包含的对象的检查和执行操作”的过程:
- `ifPresent(Consumer)`:当值存在时调用 **Consumer**,否则什么也不做。
- `orElse(otherObject)`:如果值存在则直接返回,否则生成 **otherObject**。
@@ -1297,17 +1298,21 @@ Epithets
Caught java.lang.Exception: Supplied
```
-` test()` 通过使用与所有示例方法匹配的 **Consumer** 来防止代码重复。`orElseThrow()` 使用 **catch** 关键字来捕获 `orElseThrow()` 抛出的异常。你将会在 [异常]() 这一章节中学习细节。
+`test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。
-### 创建 Optional(Creating Optionals)
+`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在[异常](./15-Exceptions.md) 这一章节中学习。
-当你编写自己的代码生成 **Optional** 时,这里有 3 个你可以使用的静态方法:
-- `empty()`:生成一个内部没有任何东西的 **Optional**。
-- `of(value)`:如果你已经确定值不为空,使用这个方法将值包装成 **Optional**。
-- `ofNullable(value)`:如果你不知道值是否为空。这个方法会在值为空的时候自动生成 **Optional.empty**,否则将值包装在 **Optional** 中。
+
+### 创建 Optional
-你可以查看这是如何工作的:
+当我们在自己的代码中加入 **Optional** 时,可以使用下面 3 个静态方法:
+
+- `empty()`:生成一个空 **Optional**。
+- `of(value)`:将一个非空值包装到 **Optional** 里。
+- `ofNullable(value)`:针对一个可能为空的值,为空时自动生成 **Optional.empty**,否则将值包装在 **Optional** 中。
+
+下面来看看它是如何工作的。代码示例:
```java
// streams/CreatingOptionals.java
@@ -1347,17 +1352,20 @@ Hi
Null
```
-如果我们试图将 `null` 传递给 `of()` 用于创建 `Optional` 对象,这就会爆炸。`ofNullable()` 会优雅地处理 `null`,所以它似乎是最安全的。
+我们不能通过传递 `null` 到 `of()` 来创建 `Optional` 对象。最安全的方法是, 使用 `ofNullable()` 来优雅地处理 `null`。
### Optional 对象操作
-3 个方法开启了 **Optional** 的后续操作,所以如果你的流管道生成了 **Optional** 对象,你可以在结尾做更多的事情:
+当我们的流管道生成了 **Optional** 对象,下面 3 个方法可使得 **Optional** 的后续能做更多的操作:
+
+- `filter(Predicate)`:将 **Predicate** 应用于 **Optional** 中的内容并返回结果。当 **Optional** 不满足 **Predicate** 时返回空。如果 **Optional** 为空,则直接返回。
+
+- `map(Function)`:如果 **Optional** 不为空,应用 **Function** 于 **Optional** 中的内容,并返回结果。否则直接返回 **Optional.empty**。
+
+- `flatMap(Function)`:同 `map()`,但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。
-- `filter(Predicate)`:将 **Predicate** 应用于 **Optional** 的内容,并将结果返回。如果 **Optional** 不满足 **Predicate**,则返回 **empty**。如果 **Optional** 已经为空,则将其返回。
-- `map(Function)`:如果 **Optional** 不为空,则将 **Function** 应用于 **Optional** 的内容,并将结果返回。否则,直接返回 **Optional.empty**。
-- `flatMap(Function)`:如同 `map()` , 但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。
+以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时删除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例:
-如上方法都不适用于数值型 **Optional**。普通流过滤器会在 **Predicate** 返回 false 时删除流元素。`Optional.filter()` 当 **Predicate** 失败时不会删除 **Optional**——而是把它留保留下来,但将其转化为空:
```java
// streams/OptionalFilter.java
@@ -1432,11 +1440,11 @@ Optional[Bingo]
Optional.empty
```
-即使输出看起来像流,但是特别注意 `test()` 中的 for 循环。它在每一次 for 循环时重新启动流,然后根据 for 循环的索引跳过指定个数的元素,这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素,结果会包装在 **Optional** 中。
+即使输出看起来像流,特别是 `test()` 中的 for 循环。每一次的 for 循环时重新启动流,然后根据 for 循环的索引跳过指定个数的元素,这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素,结果会包装在 **Optional** 中。
-值得注意的是,不同于普通的 for 循环。这里的索引值范围并不是 `i < elements.length`, 而是 `i <= elements.length`,所以最后一个元素实际上超出了流。方便的是,这将自动成为 **Optional.empty**,你可以在每一个测试的结尾中看到。
+**注意**,不同于普通 for 循环,这里的索引值范围并不是 `i < elements.length`, 而是 `i <= elements.length`。所以最后一个元素实际上超出了流。方便的是,这将自动成为 **Optional.empty**,你可以在每一个测试的结尾中看到。
-像 `map()`一样 , `Optional.map()` 应用函数,但是对于 **Optional**,它仅在 **Optional** 不为空时才应用映射函数。它还将 **Optional** 的内容提取到映射函数:
+同 `map()` 一样 , `Optional.map()` 应用于函数。它仅在 **Optional** 不为空时才应用映射函数,并将 **Optional** 的内容提取到映射函数。代码示例:
```java
// streams/OptionalMap.java
@@ -1512,9 +1520,9 @@ Optional[5]
Optional.empty
```
-映射函数的返回结果会自动包装成为 **Optional**。正如你所看到的,**Optional.empty** 会被直接跳过不使用任何映射函数。
+映射函数的返回结果会自动包装成为 **Optional**。**Optional.empty** 会被直接跳过。
-对于 **Optional** 的 `flatMap()` 应用于已经生成 **Optional** 的映射函数,所以 `flatMap()` 不会像 `map()` 所做的那样将结果封装在 **Optional** 中:
+**Optional** 的 `flatMap()` 应用于已生成 **Optional** 的映射函数,所以 `flatMap()` 不会像 `map()` 那样将结果封装在 **Optional** 中。代码示例:
```java
// streams/OptionalFlatMap.java
@@ -1592,11 +1600,12 @@ Optional[5]
Optional.empty
```
-如同 `map()` 一样,`flatMap()` 将解压非空 **Optional** 的内容并将其应用在映射函数。唯一的区别就是 `flatMap()` 不会把结果包装在 **Optional** 中,因为映射函数已经做了这件事情。在如上的示例中,我已经在每一个映射函数中显式地完成了包装,但是很显然 `Optional.flatMap()` 是为那些自己已经生成 **Optional** 的函数而设计的。
+同 `map()`,`flatMap()` 将提取非空 **Optional** 的内容并将其应用在映射函数。唯一的区别就是 `flatMap()` 不会把结果包装在 **Optional** 中,因为映射函数已经被包装过了。在如上示例中,我们已经在每一个映射函数中显式地完成了包装,但是很显然 `Optional.flatMap()` 是为那些自己已经生成 **Optional** 的函数而设计的。
-### Optional 流(Streams of Optionals)
+
+### Optional 流
-假设你有一个可能产生 `null` 的生成器。如果你使用这个生成器来创建流,你会自然的想用 **Optional** 来包装元素。如下是它的样子:
+假设你的生成器可能产生 `null` 值,那么当用它来创建流时,你会自然地想到用 **Optional** 来包装元素。如下是它的样子,代码示例:
```java
// streams/Signal.java
@@ -1626,7 +1635,7 @@ public class Signal {
}
```
-当你想使用这个流的时候,你必须弄清楚如何解包 **Optional**:
+当我们使用这个流的时候,必须要弄清楚如何解包 **Optional**。代码示例:
```java
// streams/StreamOfOptionals.java
@@ -1667,7 +1676,7 @@ Signal(dash)
Signal(dash)
```
-在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。因为每一种情况都需要你决定“空值”的含义,所以通常要为每个应用程序采用不同的方法。
+在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的行为。
From 981dd72d281b256246d141d83e36f67bae89bfb8 Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Sun, 25 Aug 2019 23:50:07 +0800
Subject: [PATCH 045/449] =?UTF-8?q?=E8=B0=83=E6=95=B4docsify=E4=BE=A7?=
=?UTF-8?q?=E8=BE=B9=E6=A0=8F=E6=98=BE=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/_coverpage.md | 2 +-
docs/_sidebar.md | 392 ---------------------------------------------
docs/index.html | 4 +-
docs/sidebar.md | 392 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 395 insertions(+), 395 deletions(-)
delete mode 100644 docs/_sidebar.md
create mode 100644 docs/sidebar.md
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
index 11b556af..09ca8f43 100644
--- a/docs/_coverpage.md
+++ b/docs/_coverpage.md
@@ -15,7 +15,7 @@
[GitHub](https://github.com/lingcoder/onJava8/)
-[Get Started](README.md)
+[Get Started](sidebar.md)
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
deleted file mode 100644
index eeef0d07..00000000
--- a/docs/_sidebar.md
+++ /dev/null
@@ -1,392 +0,0 @@
-
-* [译者的话](README.md)
-* [封面](book/00-On-Java-8.md)
-* [前言](book/00-Preface.md)
-* [简介](book/00-Introduction.md)
-* [第一章 对象的概念](book/01-What-is-an-Object.md)
- * [抽象](book/01-What-is-an-Object.md#抽象)
- * [接口](book/01-What-is-an-Object.md#接口)
- * [服务提供](book/01-What-is-an-Object.md#服务提供)
- * [封装](book/01-What-is-an-Object.md#封装)
- * [复用](book/01-What-is-an-Object.md#复用)
- * [继承](book/01-What-is-an-Object.md#继承)
- * [多态](book/01-What-is-an-Object.md#多态)
- * [单继承](book/01-What-is-an-Object.md#单继承)
- * [集合](book/01-What-is-an-Object.md#集合)
- * [生命周期](book/01-What-is-an-Object.md#生命周期)
- * [异常处理](book/01-What-is-an-Object.md#异常处理)
- * [本章小结](book/01-What-is-an-Object.md#本章小结)
-* [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md)
- * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器)
- * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell)
- * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装)
- * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装)
- * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例)
-* [第三章 万物皆对象](book/03-Objects-Everywhere.md)
- * [对象操纵](book/03-Objects-Everywhere.md#对象操纵)
- * [对象创建](book/03-Objects-Everywhere.md#对象创建)
- * [代码注释](book/03-Objects-Everywhere.md#代码注释)
- * [对象清理](book/03-Objects-Everywhere.md#对象清理)
- * [类的创建](book/03-Objects-Everywhere.md#类的创建)
- * [程序编写](book/03-Objects-Everywhere.md#程序编写)
- * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀)
- * [编码风格](book/03-Objects-Everywhere.md#编码风格)
- * [本章小结](book/03-Objects-Everywhere.md#本章小结)
-* [第四章 运算符](book/04-Operators.md)
- * [使用说明](book/04-Operators.md#使用说明)
- * [优先级](book/04-Operators.md#优先级)
- * [赋值](book/04-Operators.md#赋值)
- * [算术运算符](book/04-Operators.md#算术运算符)
- * [递增和递减](book/04-Operators.md#递增和递减)
- * [关系运算符](book/04-Operators.md#关系运算符)
- * [逻辑运算符](book/04-Operators.md#逻辑运算符)
- * [字面值常量](book/04-Operators.md#字面值常量)
- * [按位运算符](book/04-Operators.md#按位运算符)
- * [移位运算符](book/04-Operators.md#移位运算符)
- * [三元运算符](book/04-Operators.md#三元运算符)
- * [字符串运算符](book/04-Operators.md#字符串运算符)
- * [常见陷阱](book/04-Operators.md#常见陷阱)
- * [类型转换](book/04-Operators.md#类型转换)
- * [Java没有sizeof](book/04-Operators.md#Java没有sizeof)
- * [运算符总结](book/04-Operators.md#运算符总结)
- * [本章小结](book/04-Operators.md#本章小结)
-* [第五章 控制流](book/05-Control-Flow.md)
- * [true和flase](book/05-Control-Flow.md#true和flase)
- * [if-else](book/05-Control-Flow.md#if-else)
- * [迭代语句](book/05-Control-Flow.md#迭代语句)
- * [for-in语法](book/05-Control-Flow.md#for-in语法)
- * [return](book/05-Control-Flow.md#return)
- * [break和continue](book/05-Control-Flow.md#break和continue)
- * [臭名昭著的goto](book/05-Control-Flow.md#臭名昭著的goto)
- * [switch](book/05-Control-Flow.md#switch)
- * [switch字符串](book/05-Control-Flow.md#switch字符串)
- * [本章小结](book/05-Control-Flow.md#本章小结)
-* [第六章 初始化和清理](book/06-Housekeeping.md)
- * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化)
- * [方法重载](book/06-Housekeeping.md#方法重载)
- * [无参构造器](book/06-Housekeeping.md#无参构造器)
- * [this关键字](book/06-Housekeeping.md#this关键字)
- * [垃圾回收器](book/06-Housekeeping.md#垃圾回收器)
- * [成员初始化](book/06-Housekeeping.md#成员初始化)
- * [构造器初始化](book/06-Housekeeping.md#构造器初始化)
- * [数组初始化](book/06-Housekeeping.md#数组初始化)
- * [枚举类型](book/06-Housekeeping.md#枚举类型)
- * [本章小结](book/06-Housekeeping.md#本章小结)
-* [第七章 封装](book/07-Implementation-Hiding.md)
- * [包的概念](book/07-Implementation-Hiding.md#包的概念)
- * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符)
- * [接口和实现](book/07-Implementation-Hiding.md#接口和实现)
- * [类访问权限](book/07-Implementation-Hiding.md#类访问权限)
- * [本章小结](book/07-Implementation-Hiding.md#本章小结)
-* [第八章 复用](book/08-Reuse.md)
- * [组合语法](book/08-Reuse.md#组合语法)
- * [继承语法](book/08-Reuse.md#继承语法)
- * [委托](book/08-Reuse.md#委托)
- * [结合组合与继承](book/08-Reuse.md#结合组合与继承)
- * [组合与继承的选择](book/08-Reuse.md#组合与继承的选择)
- * [protected](book/08-Reuse.md#protected)
- * [向上转型](book/08-Reuse.md#向上转型)
- * [final关键字](book/08-Reuse.md#final关键字)
- * [类初始化和加载](book/08-Reuse.md#类初始化和加载)
- * [本章小结](book/08-Reuse.md#本章小结)
-* [第九章 多态](book/09-Polymorphism.md)
- * [向上转型回溯](book/09-Polymorphism.md#向上转型回溯)
- * [深入理解](book/09-Polymorphism.md#深入理解)
- * [构造器和多态](book/09-Polymorphism.md#构造器和多态)
- * [返回类型协变](book/09-Polymorphism.md#返回类型协变)
- * [使用继承设计](book/09-Polymorphism.md#使用继承设计)
- * [本章小结](book/09-Polymorphism.md#本章小结)
-* [第十章 接口](book/10-Interfaces.md)
- * [抽象类和方法](book/10-Interfaces.md#抽象类和方法)
- * [接口创建](book/10-Interfaces.md#接口创建)
- * [抽象类和接口](book/10-Interfaces.md#抽象类和接口)
- * [完全解耦](book/10-Interfaces.md#完全解耦)
- * [多接口结合](book/10-Interfaces.md#多接口结合)
- * [使用继承扩展接口](book/10-Interfaces.md#使用继承扩展接口)
- * [接口适配](book/10-Interfaces.md#接口适配)
- * [接口字段](book/10-Interfaces.md#接口字段)
- * [接口嵌套](book/10-Interfaces.md#接口嵌套)
- * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式)
- * [本章小结](book/10-Interfaces.md#本章小结)
-* [第十一章 内部类](book/11-Inner-Classes.md)
- * [创建内部类](book/11-Inner-Classes.md#创建内部类)
- * [链接外部类](book/11-Inner-Classes.md#链接外部类)
- * [内部类this和new的使用](book/11-Inner-Classes.md#内部类this和new的使用)
- * [内部类向上转型](book/11-Inner-Classes.md#内部类向上转型)
- * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域)
- * [匿名内部类](book/11-Inner-Classes.md#匿名内部类)
- * [嵌套类](book/11-Inner-Classes.md#嵌套类)
- * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类)
- * [继承内部类](book/11-Inner-Classes.md#继承内部类)
- * [重写内部类](book/11-Inner-Classes.md#重写内部类)
- * [内部类局部变量](book/11-Inner-Classes.md#内部类局部变量)
- * [内部类标识符](book/11-Inner-Classes.md#内部类标识符)
- * [本章小结](book/11-Inner-Classes.md#本章小结)
-* [第十二章 集合](book/12-Collections.md)
- * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合)
- * [基本概念](book/12-Collections.md#基本概念)
- * [添加元素组](book/12-Collections.md#添加元素组)
- * [集合的打印](book/12-Collections.md#集合的打印)
- * [列表List](book/12-Collections.md#列表List)
- * [迭代器Iterators](book/12-Collections.md#迭代器Iterators)
- * [链表LinkedList](book/12-Collections.md#链表LinkedList)
- * [堆栈Stack](book/12-Collections.md#堆栈Stack)
- * [集合Set](book/12-Collections.md#集合Set)
- * [映射Map](book/12-Collections.md#映射Map)
- * [队列Queue](book/12-Collections.md#队列Queue)
- * [集合与迭代器](book/12-Collections.md#集合与迭代器)
- * [for-in和迭代器](book/12-Collections.md#for-in和迭代器)
- * [本章小结](book/12-Collections.md#本章小结)
-* [第十三章 函数式编程](book/13-Functional-Programming.md)
- * [新旧对比](book/13-Functional-Programming.md#新旧对比)
- * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式)
- * [方法引用](book/13-Functional-Programming.md#方法引用)
- * [函数式接口](book/13-Functional-Programming.md#函数式接口)
- * [高阶函数](book/13-Functional-Programming.md#高阶函数)
- * [闭包](book/13-Functional-Programming.md#闭包)
- * [函数组合](book/13-Functional-Programming.md#函数组合)
- * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值)
- * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程)
- * [本章小结](book/13-Functional-Programming.md#本章小结)
-* [第十四章 流式编程](book/14-Streams.md)
- * [流支持](book/14-Streams.md#流支持)
- * [流创建](book/14-Streams.md#流创建)
- * [中级流操作](book/14-Streams.md#中级流操作)
- * [Optional类](book/14-Streams.md#Optional类)
- * [终端操作](book/14-Streams.md#终端操作)
- * [本章小结](book/14-Streams.md#本章小结)
-* [第十五章 异常](book/15-Exceptions.md)
- * [异常概念](book/15-Exceptions.md#异常概念)
- * [基本异常](book/15-Exceptions.md#基本异常)
- * [异常捕获](book/15-Exceptions.md#异常捕获)
- * [自定义异常](book/15-Exceptions.md#自定义异常)
- * [异常规范](book/15-Exceptions.md#异常规范)
- * [任意异常捕获](book/15-Exceptions.md#任意异常捕获)
- * [Java标准异常](book/15-Exceptions.md#Java标准异常)
- * [finally关键字](book/15-Exceptions.md#finally关键字)
- * [异常限制](book/15-Exceptions.md#异常限制)
- * [异常构造](book/15-Exceptions.md#异常构造)
- * [Try-With-Resources用法](book/15-Exceptions.md#Try-With-Resources用法)
- * [异常匹配](book/15-Exceptions.md#异常匹配)
- * [异常准则](book/15-Exceptions.md#异常准则)
- * [异常指南](book/15-Exceptions.md#异常指南)
- * [本章小结](book/15-Exceptions.md#本章小结)
-* [第十六章 代码校验](book/16-Validating-Your-Code.md)
- * [测试](book/16-Validating-Your-Code.md#测试)
- * [前提条件](book/16-Validating-Your-Code.md#前提条件)
- * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发)
- * [日志](book/16-Validating-Your-Code.md#日志)
- * [调试](book/16-Validating-Your-Code.md#调试)
- * [基准测试](book/16-Validating-Your-Code.md#基准测试)
- * [分析和优化](book/16-Validating-Your-Code.md#分析和优化)
- * [风格检测](book/16-Validating-Your-Code.md#风格检测)
- * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析)
- * [代码重审](book/16-Validating-Your-Code.md#代码重审)
- * [结对编程](book/16-Validating-Your-Code.md#结对编程)
- * [重构](book/16-Validating-Your-Code.md#重构)
- * [持续集成](book/16-Validating-Your-Code.md#持续集成)
- * [本章小结](book/16-Validating-Your-Code.md#本章小结)
-* [第十七章 文件](book/17-Files.md)
- * [文件和目录路径](book/17-Files.md#文件和目录路径)
- * [目录](book/17-Files.md#目录)
- * [文件系统](book/17-Files.md#文件系统)
- * [路径监听](book/17-Files.md#路径监听)
- * [文件查找](book/17-Files.md#文件查找)
- * [文件读写](book/17-Files.md#文件读写)
- * [本章小结](book/17-Files.md#本章小结)
-* [第十八章 字符串](book/18-Strings.md)
- * [字符串的不可变](book/18-Strings.md#字符串的不可变)
- * [重载和StringBuilder](book/18-Strings.md#重载和StringBuilder)
- * [意外递归](book/18-Strings.md#意外递归)
- * [字符串操作](book/18-Strings.md#字符串操作)
- * [格式化输出](book/18-Strings.md#格式化输出)
- * [常规表达式](book/18-Strings.md#常规表达式)
- * [扫描输入](book/18-Strings.md#扫描输入)
- * [StringTokenizer类](book/18-Strings.md#StringTokenizer类)
- * [本章小结](book/18-Strings.md#本章小结)
-* [第十九章 类型信息](book/19-Type-Information.md)
- * [运行时类型信息](book/19-Type-Information.md#运行时类型信息)
- * [类的对象](book/19-Type-Information.md#类的对象)
- * [类型转换检测](book/19-Type-Information.md#类型转换检测)
- * [注册工厂](book/19-Type-Information.md#注册工厂)
- * [类的等价比较](book/19-Type-Information.md#类的等价比较)
- * [反射运行时类信息](book/19-Type-Information.md#反射运行时类信息)
- * [动态代理](book/19-Type-Information.md#动态代理)
- * [Optional类](book/19-Type-Information.md#Optional类)
- * [接口和类型](book/19-Type-Information.md#接口和类型)
- * [本章小结](book/19-Type-Information.md#本章小结)
-* [第二十章 泛型](book/20-Generics.md)
- * [简单泛型](book/20-Generics.md#简单泛型)
- * [泛型接口](book/20-Generics.md#泛型接口)
- * [泛型方法](book/20-Generics.md#泛型方法)
- * [复杂模型构建](book/20-Generics.md#复杂模型构建)
- * [泛型擦除](book/20-Generics.md#泛型擦除)
- * [补偿擦除](book/20-Generics.md#补偿擦除)
- * [边界](book/20-Generics.md#边界)
- * [通配符](book/20-Generics.md#通配符)
- * [问题](book/20-Generics.md#问题)
- * [自我约束类型](book/20-Generics.md#自我约束类型)
- * [动态类型安全](book/20-Generics.md#动态类型安全)
- * [泛型异常](book/20-Generics.md#泛型异常)
- * [混入](book/20-Generics.md#混入)
- * [潜在类型](book/20-Generics.md#潜在类型)
- * [补偿不足](book/20-Generics.md#补偿不足)
- * [辅助潜在类型](book/20-Generics.md#辅助潜在类型)
- * [泛型的优劣](book/20-Generics.md#泛型的优劣)
-* [第二十一章 数组](book/21-Arrays.md)
- * [数组特性](book/21-Arrays.md#数组特性)
- * [一等对象](book/21-Arrays.md#一等对象)
- * [返回数组](book/21-Arrays.md#返回数组)
- * [多维数组](book/21-Arrays.md#多维数组)
- * [泛型数组](book/21-Arrays.md#泛型数组)
- * [Arrays的fill方法](book/21-Arrays.md#Arrays的fill方法)
- * [Arrays的setAll方法](book/21-Arrays.md#Arrays的setAll方法)
- * [增量生成](book/21-Arrays.md#增量生成)
- * [随机生成](book/21-Arrays.md#随机生成)
- * [泛型和基本数组](book/21-Arrays.md#泛型和基本数组)
- * [数组元素修改](book/21-Arrays.md#数组元素修改)
- * [数组并行](book/21-Arrays.md#数组并行)
- * [Arrays工具类](book/21-Arrays.md#Arrays工具类)
- * [数组拷贝](book/21-Arrays.md#数组拷贝)
- * [数组比较](book/21-Arrays.md#数组比较)
- * [流和数组](book/21-Arrays.md#流和数组)
- * [数组排序](book/21-Arrays.md#数组排序)
- * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找)
- * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀)
- * [本章小结](book/21-Arrays.md#本章小结)
-* [第二十二章 枚举](book/22-Enumerations.md)
- * [基本功能](book/22-Enumerations.md#基本功能)
- * [方法添加](book/22-Enumerations.md#方法添加)
- * [switch语句](book/22-Enumerations.md#switch语句)
- * [values方法](book/22-Enumerations.md#values方法)
- * [实现而非继承](book/22-Enumerations.md#实现而非继承)
- * [随机选择](book/22-Enumerations.md#随机选择)
- * [使用接口组织](book/22-Enumerations.md#使用接口组织)
- * [使用EnumSet替代Flags](book/22-Enumerations.md#使用EnumSet替代Flags)
- * [使用EnumMap](book/22-Enumerations.md#使用EnumMap)
- * [常量特定方法](book/22-Enumerations.md#常量特定方法)
- * [多次调度](book/22-Enumerations.md#多次调度)
- * [本章小结](book/22-Enumerations.md#本章小结)
-* [第二十三章 注解](book/23-Annotations.md)
- * [基本语法](book/23-Annotations.md#基本语法)
- * [编写注解处理器](book/23-Annotations.md#编写注解处理器)
- * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解)
- * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试)
- * [本章小结](book/23-Annotations.md#本章小结)
-* [第二十四章 并发编程](book/24-Concurrent-Programming.md)
- * [术语问题](book/24-Concurrent-Programming.md#术语问题)
- * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力)
- * [针对速度](book/24-Concurrent-Programming.md#针对速度)
- * [四句格言](book/24-Concurrent-Programming.md#四句格言)
- * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相)
- * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分)
- * [并行流](book/24-Concurrent-Programming.md#并行流)
- * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务)
- * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务)
- * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类)
- * [死锁](book/24-Concurrent-Programming.md#死锁)
- * [构造函数非线程安全](book/24-Concurrent-Programming.md#构造函数非线程安全)
- * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价)
- * [本章小结](book/24-Concurrent-Programming.md#本章小结)
-* [第二十五章 设计模式](book/25-Patterns.md)
- * [概念](book/25-Patterns.md#概念)
- * [构建型](book/25-Patterns.md#构建型)
- * [面向实施](book/25-Patterns.md#面向实施)
- * [工厂模式](book/25-Patterns.md#工厂模式)
- * [函数对象](book/25-Patterns.md#函数对象)
- * [接口改变](book/25-Patterns.md#接口改变)
- * [解释器](book/25-Patterns.md#解释器)
- * [回调](book/25-Patterns.md#回调)
- * [多次调度](book/25-Patterns.md#多次调度)
- * [模式重构](book/25-Patterns.md#模式重构)
- * [抽象用法](book/25-Patterns.md#抽象用法)
- * [多次派遣](book/25-Patterns.md#多次派遣)
- * [访问者模式](book/25-Patterns.md#访问者模式)
- * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣)
- * [本章小结](book/25-Patterns.md#本章小结)
-
-* [附录:补充](book/Appendix-Supplements.md)
- * [可下载的补充](book/Appendix-Supplements.md#可下载的补充)
- * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础)
- * [动手实践](book/Appendix-Supplements.md#动手实践)
-* [附录:编程指南](book/Appendix-Programming-Guidelines.md)
- * [设计](book/Appendix-Programming-Guidelines.md#设计)
- * [实现](book/Appendix-Programming-Guidelines.md#实现)
-* [附录:文档注释](book/Appendix-Javadoc.md)
-* [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md)
- * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用)
- * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝)
- * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆)
- * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类)
- * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结)
-* [附录:流式IO](book/Appendix-IO-Streams.md)
- * [输入流类型](book/Appendix-IO-Streams.md#输入流类型)
- * [输出流类型](book/Appendix-IO-Streams.md#输出流类型)
- * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口)
- * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer)
- * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类)
- * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途)
- * [本章小结](book/Appendix-IO-Streams.md#本章小结)
-* [附录:标准IO](book/Appendix-Standard-IO.md)
- * [执行控制](book/Appendix-Standard-IO.md#执行控制)
-* [附录:新IO](book/Appendix-New-IO.md)
- * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer)
- * [转换数据](book/Appendix-New-IO.md#数据转换)
- * [获取原始类型](book/Appendix-New-IO.md#基本类型获取)
- * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区)
- * [使用缓冲区进行数据操作](book/Appendix-New-IO.md#缓冲区数据操作)
- * [内存映射文件](book/Appendix-New-IO.md#内存映射文件)
- * [文件锁定](book/Appendix-New-IO.md#文件锁定)
-* [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md)
- * [equals典范](book/Appendix-Understanding-equals-and-hashCode.md#equals典范)
- * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码)
- * [调整HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap)
-* [附录:集合主题](book/Appendix-Collection-Topics.md)
- * [示例数据](book/Appendix-Collection-Topics.md#示例数据)
- * [List表现](book/Appendix-Collection-Topics.md#List表现)
- * [Set表现](book/Appendix-Collection-Topics.md#Set表现)
- * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作)
- * [选择Map的部分](book/Appendix-Collection-Topics.md#选择Map的部分)
- * [集合的fill方法](book/Appendix-Collection-Topics.md#集合的fill方法)
- * [使用Flyweight自定义集合和Map](book/Appendix-Collection-Topics.md#使用Flyweight自定义集合和Map)
- * [集合功能](book/Appendix-Collection-Topics.md#集合功能)
- * [可选操作](book/Appendix-Collection-Topics.md#可选操作)
- * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序)
- * [队列](book/Appendix-Collection-Topics.md#队列)
- * [理解Map](book/Appendix-Collection-Topics.md#理解Map)
- * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类)
- * [持有引用](book/Appendix-Collection-Topics.md#持有引用)
- * [避免旧式类库](book/Appendix-Collection-Topics.md#避免旧式类库)
- * [本章小结](book/Appendix-Collection-Topics.md#本章小结)
-* [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md)
- * [线程](book/Appendix-Low-Level-Concurrency.md#线程)
- * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获)
- * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享)
- * [volatile关键字](book/Appendix-Low-Level-Concurrency.md#volatile关键字)
- * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性)
- * [关键部分](book/Appendix-Low-Level-Concurrency.md#关键部分)
- * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件)
- * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结)
-* [附录:数据压缩](book/Appendix-Data-Compression.md)
- * [使用Gzip简单压缩](book/Appendix-Data-Compression.md#使用Gzip简单压缩)
- * [使用zip多文件存储](book/Appendix-Data-Compression.md#使用zip多文件存储)
- * [Java的jar](book/Appendix-Data-Compression.md#Java的jar)
-* [附录:对象序列化](book/Appendix-Object-Serialization.md)
- * [查找类](book/Appendix-Object-Serialization.md#查找类)
- * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化)
- * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化)
-* [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md)
- * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言)
- * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试)
- * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字)
- * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本)
- * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态)
-* [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md)
-* [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md)
- * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始)
- * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯)
- * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话)
- * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手)
- * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程)
- * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事)
-* [词汇表](book/GLOSSARY.md)
diff --git a/docs/index.html b/docs/index.html
index 673a7d64..7f54b8af 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -345,8 +345,8 @@
window.$docsify = {
name: '《On Java 8》中文版',
repo: 'https://github.com/LingCoder/OnJava8',
- loadSidebar: true,
- subMaxLevel: 1,
+ // loadSidebar: true,
+ subMaxLevel: 3,
search: {
paths: 'auto',
placeholder: '🔍 点击搜索 ',
diff --git a/docs/sidebar.md b/docs/sidebar.md
new file mode 100644
index 00000000..8be7bada
--- /dev/null
+++ b/docs/sidebar.md
@@ -0,0 +1,392 @@
+
+#### [译者的话](README.md)
+#### [封面](book/00-On-Java-8.md)
+#### [前言](book/00-Preface.md)
+#### [简介](book/00-Introduction.md)
+## [第一章 对象的概念](book/01-What-is-an-Object.md)
+ * [抽象](book/01-What-is-an-Object.md#抽象)
+ * [接口](book/01-What-is-an-Object.md#接口)
+ * [服务提供](book/01-What-is-an-Object.md#服务提供)
+ * [封装](book/01-What-is-an-Object.md#封装)
+ * [复用](book/01-What-is-an-Object.md#复用)
+## [继承](book/01-What-is-an-Object.md#继承)
+ * [多态](book/01-What-is-an-Object.md#多态)
+ * [单继承](book/01-What-is-an-Object.md#单继承)
+ * [集合](book/01-What-is-an-Object.md#集合)
+ * [生命周期](book/01-What-is-an-Object.md#生命周期)
+ * [异常处理](book/01-What-is-an-Object.md#异常处理)
+ * [本章小结](book/01-What-is-an-Object.md#本章小结)
+## [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md)
+ * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器)
+ * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell)
+ * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装)
+ * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装)
+ * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例)
+## [第三章 万物皆对象](book/03-Objects-Everywhere.md)
+ * [对象操纵](book/03-Objects-Everywhere.md#对象操纵)
+ * [对象创建](book/03-Objects-Everywhere.md#对象创建)
+ * [代码注释](book/03-Objects-Everywhere.md#代码注释)
+ * [对象清理](book/03-Objects-Everywhere.md#对象清理)
+ * [类的创建](book/03-Objects-Everywhere.md#类的创建)
+ * [程序编写](book/03-Objects-Everywhere.md#程序编写)
+ * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀)
+ * [编码风格](book/03-Objects-Everywhere.md#编码风格)
+ * [本章小结](book/03-Objects-Everywhere.md#本章小结)
+## [第四章 运算符](book/04-Operators.md)
+ * [使用说明](book/04-Operators.md#使用说明)
+ * [优先级](book/04-Operators.md#优先级)
+ * [赋值](book/04-Operators.md#赋值)
+ * [算术运算符](book/04-Operators.md#算术运算符)
+ * [递增和递减](book/04-Operators.md#递增和递减)
+ * [关系运算符](book/04-Operators.md#关系运算符)
+ * [逻辑运算符](book/04-Operators.md#逻辑运算符)
+ * [字面值常量](book/04-Operators.md#字面值常量)
+ * [按位运算符](book/04-Operators.md#按位运算符)
+ * [移位运算符](book/04-Operators.md#移位运算符)
+ * [三元运算符](book/04-Operators.md#三元运算符)
+ * [字符串运算符](book/04-Operators.md#字符串运算符)
+ * [常见陷阱](book/04-Operators.md#常见陷阱)
+ * [类型转换](book/04-Operators.md#类型转换)
+ * [Java没有sizeof](book/04-Operators.md#Java没有sizeof)
+ * [运算符总结](book/04-Operators.md#运算符总结)
+ * [本章小结](book/04-Operators.md#本章小结)
+## [第五章 控制流](book/05-Control-Flow.md)
+ * [true和flase](book/05-Control-Flow.md#true和flase)
+ * [if-else](book/05-Control-Flow.md#if-else)
+ * [迭代语句](book/05-Control-Flow.md#迭代语句)
+ * [for-in语法](book/05-Control-Flow.md#for-in语法)
+ * [return](book/05-Control-Flow.md#return)
+ * [break和continue](book/05-Control-Flow.md#break和continue)
+ * [臭名昭著的goto](book/05-Control-Flow.md#臭名昭著的goto)
+ * [switch](book/05-Control-Flow.md#switch)
+ * [switch字符串](book/05-Control-Flow.md#switch字符串)
+ * [本章小结](book/05-Control-Flow.md#本章小结)
+## [第六章 初始化和清理](book/06-Housekeeping.md)
+ * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化)
+ * [方法重载](book/06-Housekeeping.md#方法重载)
+ * [无参构造器](book/06-Housekeeping.md#无参构造器)
+ * [this关键字](book/06-Housekeeping.md#this关键字)
+ * [垃圾回收器](book/06-Housekeeping.md#垃圾回收器)
+ * [成员初始化](book/06-Housekeeping.md#成员初始化)
+ * [构造器初始化](book/06-Housekeeping.md#构造器初始化)
+ * [数组初始化](book/06-Housekeeping.md#数组初始化)
+ * [枚举类型](book/06-Housekeeping.md#枚举类型)
+ * [本章小结](book/06-Housekeeping.md#本章小结)
+## [第七章 封装](book/07-Implementation-Hiding.md)
+ * [包的概念](book/07-Implementation-Hiding.md#包的概念)
+ * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符)
+ * [接口和实现](book/07-Implementation-Hiding.md#接口和实现)
+ * [类访问权限](book/07-Implementation-Hiding.md#类访问权限)
+ * [本章小结](book/07-Implementation-Hiding.md#本章小结)
+## [第八章 复用](book/08-Reuse.md)
+ * [组合语法](book/08-Reuse.md#组合语法)
+ * [继承语法](book/08-Reuse.md#继承语法)
+ * [委托](book/08-Reuse.md#委托)
+ * [结合组合与继承](book/08-Reuse.md#结合组合与继承)
+ * [组合与继承的选择](book/08-Reuse.md#组合与继承的选择)
+ * [protected](book/08-Reuse.md#protected)
+ * [向上转型](book/08-Reuse.md#向上转型)
+ * [final关键字](book/08-Reuse.md#final关键字)
+ * [类初始化和加载](book/08-Reuse.md#类初始化和加载)
+ * [本章小结](book/08-Reuse.md#本章小结)
+## [第九章 多态](book/09-Polymorphism.md)
+ * [向上转型回溯](book/09-Polymorphism.md#向上转型回溯)
+ * [深入理解](book/09-Polymorphism.md#深入理解)
+ * [构造器和多态](book/09-Polymorphism.md#构造器和多态)
+ * [返回类型协变](book/09-Polymorphism.md#返回类型协变)
+ * [使用继承设计](book/09-Polymorphism.md#使用继承设计)
+ * [本章小结](book/09-Polymorphism.md#本章小结)
+## [第十章 接口](book/10-Interfaces.md)
+ * [抽象类和方法](book/10-Interfaces.md#抽象类和方法)
+ * [接口创建](book/10-Interfaces.md#接口创建)
+ * [抽象类和接口](book/10-Interfaces.md#抽象类和接口)
+ * [完全解耦](book/10-Interfaces.md#完全解耦)
+ * [多接口结合](book/10-Interfaces.md#多接口结合)
+ * [使用继承扩展接口](book/10-Interfaces.md#使用继承扩展接口)
+ * [接口适配](book/10-Interfaces.md#接口适配)
+ * [接口字段](book/10-Interfaces.md#接口字段)
+ * [接口嵌套](book/10-Interfaces.md#接口嵌套)
+ * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式)
+ * [本章小结](book/10-Interfaces.md#本章小结)
+## [第十一章 内部类](book/11-Inner-Classes.md)
+ * [创建内部类](book/11-Inner-Classes.md#创建内部类)
+ * [链接外部类](book/11-Inner-Classes.md#链接外部类)
+ * [内部类this和new的使用](book/11-Inner-Classes.md#内部类this和new的使用)
+ * [内部类向上转型](book/11-Inner-Classes.md#内部类向上转型)
+ * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域)
+ * [匿名内部类](book/11-Inner-Classes.md#匿名内部类)
+ * [嵌套类](book/11-Inner-Classes.md#嵌套类)
+ * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类)
+ * [继承内部类](book/11-Inner-Classes.md#继承内部类)
+ * [重写内部类](book/11-Inner-Classes.md#重写内部类)
+ * [内部类局部变量](book/11-Inner-Classes.md#内部类局部变量)
+ * [内部类标识符](book/11-Inner-Classes.md#内部类标识符)
+ * [本章小结](book/11-Inner-Classes.md#本章小结)
+## [第十二章 集合](book/12-Collections.md)
+ * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合)
+ * [基本概念](book/12-Collections.md#基本概念)
+ * [添加元素组](book/12-Collections.md#添加元素组)
+ * [集合的打印](book/12-Collections.md#集合的打印)
+ * [列表List](book/12-Collections.md#列表List)
+ * [迭代器Iterators](book/12-Collections.md#迭代器Iterators)
+ * [链表LinkedList](book/12-Collections.md#链表LinkedList)
+ * [堆栈Stack](book/12-Collections.md#堆栈Stack)
+ * [集合Set](book/12-Collections.md#集合Set)
+ * [映射Map](book/12-Collections.md#映射Map)
+ * [队列Queue](book/12-Collections.md#队列Queue)
+ * [集合与迭代器](book/12-Collections.md#集合与迭代器)
+ * [for-in和迭代器](book/12-Collections.md#for-in和迭代器)
+ * [本章小结](book/12-Collections.md#本章小结)
+## [第十三章 函数式编程](book/13-Functional-Programming.md)
+ * [新旧对比](book/13-Functional-Programming.md#新旧对比)
+ * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式)
+ * [方法引用](book/13-Functional-Programming.md#方法引用)
+ * [函数式接口](book/13-Functional-Programming.md#函数式接口)
+ * [高阶函数](book/13-Functional-Programming.md#高阶函数)
+ * [闭包](book/13-Functional-Programming.md#闭包)
+ * [函数组合](book/13-Functional-Programming.md#函数组合)
+ * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值)
+ * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程)
+ * [本章小结](book/13-Functional-Programming.md#本章小结)
+## [第十四章 流式编程](book/14-Streams.md)
+ * [流支持](book/14-Streams.md#流支持)
+ * [流创建](book/14-Streams.md#流创建)
+ * [中级流操作](book/14-Streams.md#中级流操作)
+ * [Optional类](book/14-Streams.md#Optional类)
+ * [终端操作](book/14-Streams.md#终端操作)
+ * [本章小结](book/14-Streams.md#本章小结)
+## [第十五章 异常](book/15-Exceptions.md)
+ * [异常概念](book/15-Exceptions.md#异常概念)
+ * [基本异常](book/15-Exceptions.md#基本异常)
+ * [异常捕获](book/15-Exceptions.md#异常捕获)
+ * [自定义异常](book/15-Exceptions.md#自定义异常)
+ * [异常规范](book/15-Exceptions.md#异常规范)
+ * [任意异常捕获](book/15-Exceptions.md#任意异常捕获)
+ * [Java标准异常](book/15-Exceptions.md#Java标准异常)
+ * [finally关键字](book/15-Exceptions.md#finally关键字)
+ * [异常限制](book/15-Exceptions.md#异常限制)
+ * [异常构造](book/15-Exceptions.md#异常构造)
+ * [Try-With-Resources用法](book/15-Exceptions.md#Try-With-Resources用法)
+ * [异常匹配](book/15-Exceptions.md#异常匹配)
+ * [异常准则](book/15-Exceptions.md#异常准则)
+ * [异常指南](book/15-Exceptions.md#异常指南)
+ * [本章小结](book/15-Exceptions.md#本章小结)
+## [第十六章 代码校验](book/16-Validating-Your-Code.md)
+ * [测试](book/16-Validating-Your-Code.md#测试)
+ * [前提条件](book/16-Validating-Your-Code.md#前提条件)
+ * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发)
+ * [日志](book/16-Validating-Your-Code.md#日志)
+ * [调试](book/16-Validating-Your-Code.md#调试)
+ * [基准测试](book/16-Validating-Your-Code.md#基准测试)
+ * [分析和优化](book/16-Validating-Your-Code.md#分析和优化)
+ * [风格检测](book/16-Validating-Your-Code.md#风格检测)
+ * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析)
+ * [代码重审](book/16-Validating-Your-Code.md#代码重审)
+ * [结对编程](book/16-Validating-Your-Code.md#结对编程)
+ * [重构](book/16-Validating-Your-Code.md#重构)
+ * [持续集成](book/16-Validating-Your-Code.md#持续集成)
+ * [本章小结](book/16-Validating-Your-Code.md#本章小结)
+## [第十七章 文件](book/17-Files.md)
+ * [文件和目录路径](book/17-Files.md#文件和目录路径)
+ * [目录](book/17-Files.md#目录)
+ * [文件系统](book/17-Files.md#文件系统)
+ * [路径监听](book/17-Files.md#路径监听)
+ * [文件查找](book/17-Files.md#文件查找)
+ * [文件读写](book/17-Files.md#文件读写)
+ * [本章小结](book/17-Files.md#本章小结)
+## [第十八章 字符串](book/18-Strings.md)
+ * [字符串的不可变](book/18-Strings.md#字符串的不可变)
+ * [重载和StringBuilder](book/18-Strings.md#重载和StringBuilder)
+ * [意外递归](book/18-Strings.md#意外递归)
+ * [字符串操作](book/18-Strings.md#字符串操作)
+ * [格式化输出](book/18-Strings.md#格式化输出)
+ * [常规表达式](book/18-Strings.md#常规表达式)
+ * [扫描输入](book/18-Strings.md#扫描输入)
+ * [StringTokenizer类](book/18-Strings.md#StringTokenizer类)
+ * [本章小结](book/18-Strings.md#本章小结)
+## [第十九章 类型信息](book/19-Type-Information.md)
+ * [运行时类型信息](book/19-Type-Information.md#运行时类型信息)
+ * [类的对象](book/19-Type-Information.md#类的对象)
+ * [类型转换检测](book/19-Type-Information.md#类型转换检测)
+ * [注册工厂](book/19-Type-Information.md#注册工厂)
+ * [类的等价比较](book/19-Type-Information.md#类的等价比较)
+ * [反射运行时类信息](book/19-Type-Information.md#反射运行时类信息)
+ * [动态代理](book/19-Type-Information.md#动态代理)
+ * [Optional类](book/19-Type-Information.md#Optional类)
+ * [接口和类型](book/19-Type-Information.md#接口和类型)
+ * [本章小结](book/19-Type-Information.md#本章小结)
+## [第二十章 泛型](book/20-Generics.md)
+ * [简单泛型](book/20-Generics.md#简单泛型)
+ * [泛型接口](book/20-Generics.md#泛型接口)
+ * [泛型方法](book/20-Generics.md#泛型方法)
+ * [复杂模型构建](book/20-Generics.md#复杂模型构建)
+ * [泛型擦除](book/20-Generics.md#泛型擦除)
+ * [补偿擦除](book/20-Generics.md#补偿擦除)
+ * [边界](book/20-Generics.md#边界)
+ * [通配符](book/20-Generics.md#通配符)
+ * [问题](book/20-Generics.md#问题)
+ * [自我约束类型](book/20-Generics.md#自我约束类型)
+ * [动态类型安全](book/20-Generics.md#动态类型安全)
+ * [泛型异常](book/20-Generics.md#泛型异常)
+ * [混入](book/20-Generics.md#混入)
+ * [潜在类型](book/20-Generics.md#潜在类型)
+ * [补偿不足](book/20-Generics.md#补偿不足)
+ * [辅助潜在类型](book/20-Generics.md#辅助潜在类型)
+ * [泛型的优劣](book/20-Generics.md#泛型的优劣)
+## [第二十一章 数组](book/21-Arrays.md)
+ * [数组特性](book/21-Arrays.md#数组特性)
+ * [一等对象](book/21-Arrays.md#一等对象)
+ * [返回数组](book/21-Arrays.md#返回数组)
+ * [多维数组](book/21-Arrays.md#多维数组)
+ * [泛型数组](book/21-Arrays.md#泛型数组)
+ * [Arrays的fill方法](book/21-Arrays.md#Arrays的fill方法)
+ * [Arrays的setAll方法](book/21-Arrays.md#Arrays的setAll方法)
+ * [增量生成](book/21-Arrays.md#增量生成)
+ * [随机生成](book/21-Arrays.md#随机生成)
+ * [泛型和基本数组](book/21-Arrays.md#泛型和基本数组)
+ * [数组元素修改](book/21-Arrays.md#数组元素修改)
+ * [数组并行](book/21-Arrays.md#数组并行)
+ * [Arrays工具类](book/21-Arrays.md#Arrays工具类)
+ * [数组拷贝](book/21-Arrays.md#数组拷贝)
+ * [数组比较](book/21-Arrays.md#数组比较)
+ * [流和数组](book/21-Arrays.md#流和数组)
+ * [数组排序](book/21-Arrays.md#数组排序)
+ * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找)
+ * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀)
+ * [本章小结](book/21-Arrays.md#本章小结)
+## [第二十二章 枚举](book/22-Enumerations.md)
+ * [基本功能](book/22-Enumerations.md#基本功能)
+ * [方法添加](book/22-Enumerations.md#方法添加)
+ * [switch语句](book/22-Enumerations.md#switch语句)
+ * [values方法](book/22-Enumerations.md#values方法)
+ * [实现而非继承](book/22-Enumerations.md#实现而非继承)
+ * [随机选择](book/22-Enumerations.md#随机选择)
+ * [使用接口组织](book/22-Enumerations.md#使用接口组织)
+ * [使用EnumSet替代Flags](book/22-Enumerations.md#使用EnumSet替代Flags)
+ * [使用EnumMap](book/22-Enumerations.md#使用EnumMap)
+ * [常量特定方法](book/22-Enumerations.md#常量特定方法)
+ * [多次调度](book/22-Enumerations.md#多次调度)
+ * [本章小结](book/22-Enumerations.md#本章小结)
+## [第二十三章 注解](book/23-Annotations.md)
+ * [基本语法](book/23-Annotations.md#基本语法)
+ * [编写注解处理器](book/23-Annotations.md#编写注解处理器)
+ * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解)
+ * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试)
+ * [本章小结](book/23-Annotations.md#本章小结)
+## [第二十四章 并发编程](book/24-Concurrent-Programming.md)
+ * [术语问题](book/24-Concurrent-Programming.md#术语问题)
+ * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力)
+ * [针对速度](book/24-Concurrent-Programming.md#针对速度)
+ * [四句格言](book/24-Concurrent-Programming.md#四句格言)
+ * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相)
+ * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分)
+ * [并行流](book/24-Concurrent-Programming.md#并行流)
+ * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务)
+ * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务)
+ * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类)
+ * [死锁](book/24-Concurrent-Programming.md#死锁)
+ * [构造函数非线程安全](book/24-Concurrent-Programming.md#构造函数非线程安全)
+ * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价)
+ * [本章小结](book/24-Concurrent-Programming.md#本章小结)
+## [第二十五章 设计模式](book/25-Patterns.md)
+ * [概念](book/25-Patterns.md#概念)
+ * [构建型](book/25-Patterns.md#构建型)
+ * [面向实施](book/25-Patterns.md#面向实施)
+ * [工厂模式](book/25-Patterns.md#工厂模式)
+ * [函数对象](book/25-Patterns.md#函数对象)
+ * [接口改变](book/25-Patterns.md#接口改变)
+ * [解释器](book/25-Patterns.md#解释器)
+ * [回调](book/25-Patterns.md#回调)
+ * [多次调度](book/25-Patterns.md#多次调度)
+ * [模式重构](book/25-Patterns.md#模式重构)
+ * [抽象用法](book/25-Patterns.md#抽象用法)
+ * [多次派遣](book/25-Patterns.md#多次派遣)
+ * [访问者模式](book/25-Patterns.md#访问者模式)
+ * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣)
+ * [本章小结](book/25-Patterns.md#本章小结)
+
+## [附录:补充](book/Appendix-Supplements.md)
+ * [可下载的补充](book/Appendix-Supplements.md#可下载的补充)
+ * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础)
+ * [动手实践](book/Appendix-Supplements.md#动手实践)
+## [附录:编程指南](book/Appendix-Programming-Guidelines.md)
+ * [设计](book/Appendix-Programming-Guidelines.md#设计)
+ * [实现](book/Appendix-Programming-Guidelines.md#实现)
+## [附录:文档注释](book/Appendix-Javadoc.md)
+## [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md)
+ * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用)
+ * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝)
+ * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆)
+ * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类)
+ * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结)
+## [附录:流式IO](book/Appendix-IO-Streams.md)
+ * [输入流类型](book/Appendix-IO-Streams.md#输入流类型)
+ * [输出流类型](book/Appendix-IO-Streams.md#输出流类型)
+ * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口)
+ * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer)
+ * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类)
+ * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途)
+ * [本章小结](book/Appendix-IO-Streams.md#本章小结)
+## [附录:标准IO](book/Appendix-Standard-IO.md)
+ * [执行控制](book/Appendix-Standard-IO.md#执行控制)
+## [附录:新IO](book/Appendix-New-IO.md)
+ * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer)
+ * [转换数据](book/Appendix-New-IO.md#数据转换)
+ * [获取原始类型](book/Appendix-New-IO.md#基本类型获取)
+ * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区)
+ * [使用缓冲区进行数据操作](book/Appendix-New-IO.md#缓冲区数据操作)
+ * [内存映射文件](book/Appendix-New-IO.md#内存映射文件)
+ * [文件锁定](book/Appendix-New-IO.md#文件锁定)
+## [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md)
+ * [equals典范](book/Appendix-Understanding-equals-and-hashCode.md#equals典范)
+ * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码)
+ * [调整HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap)
+## [附录:集合主题](book/Appendix-Collection-Topics.md)
+ * [示例数据](book/Appendix-Collection-Topics.md#示例数据)
+ * [List表现](book/Appendix-Collection-Topics.md#List表现)
+ * [Set表现](book/Appendix-Collection-Topics.md#Set表现)
+ * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作)
+ * [选择Map的部分](book/Appendix-Collection-Topics.md#选择Map的部分)
+ * [集合的fill方法](book/Appendix-Collection-Topics.md#集合的fill方法)
+ * [使用Flyweight自定义集合和Map](book/Appendix-Collection-Topics.md#使用Flyweight自定义集合和Map)
+ * [集合功能](book/Appendix-Collection-Topics.md#集合功能)
+ * [可选操作](book/Appendix-Collection-Topics.md#可选操作)
+ * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序)
+ * [队列](book/Appendix-Collection-Topics.md#队列)
+ * [理解Map](book/Appendix-Collection-Topics.md#理解Map)
+ * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类)
+ * [持有引用](book/Appendix-Collection-Topics.md#持有引用)
+ * [避免旧式类库](book/Appendix-Collection-Topics.md#避免旧式类库)
+ * [本章小结](book/Appendix-Collection-Topics.md#本章小结)
+## [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md)
+ * [线程](book/Appendix-Low-Level-Concurrency.md#线程)
+ * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获)
+ * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享)
+ * [volatile关键字](book/Appendix-Low-Level-Concurrency.md#volatile关键字)
+ * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性)
+ * [关键部分](book/Appendix-Low-Level-Concurrency.md#关键部分)
+ * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件)
+ * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结)
+## [附录:数据压缩](book/Appendix-Data-Compression.md)
+ * [使用Gzip简单压缩](book/Appendix-Data-Compression.md#使用Gzip简单压缩)
+ * [使用zip多文件存储](book/Appendix-Data-Compression.md#使用zip多文件存储)
+ * [Java的jar](book/Appendix-Data-Compression.md#Java的jar)
+## [附录:对象序列化](book/Appendix-Object-Serialization.md)
+ * [查找类](book/Appendix-Object-Serialization.md#查找类)
+ * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化)
+ * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化)
+## [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md)
+ * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言)
+ * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试)
+ * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字)
+ * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本)
+ * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态)
+## [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md)
+## [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md)
+ * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始)
+ * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯)
+ * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话)
+ * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手)
+ * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程)
+ * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事)
+## [词汇表](book/GLOSSARY.md)
From 83f2e7258403da072e2f4915100674f6a1fd27ab Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Sun, 25 Aug 2019 23:53:28 +0800
Subject: [PATCH 046/449] autocommit
---
docs/sidebar.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/sidebar.md b/docs/sidebar.md
index 8be7bada..e99b70ea 100644
--- a/docs/sidebar.md
+++ b/docs/sidebar.md
@@ -9,7 +9,7 @@
* [服务提供](book/01-What-is-an-Object.md#服务提供)
* [封装](book/01-What-is-an-Object.md#封装)
* [复用](book/01-What-is-an-Object.md#复用)
-## [继承](book/01-What-is-an-Object.md#继承)
+ * [继承](book/01-What-is-an-Object.md#继承)
* [多态](book/01-What-is-an-Object.md#多态)
* [单继承](book/01-What-is-an-Object.md#单继承)
* [集合](book/01-What-is-an-Object.md#集合)
From 6b5226d1a78c2d53e0f6538e9e6d8528449517ea Mon Sep 17 00:00:00 2001
From: WangHengwei
Date: Mon, 26 Aug 2019 11:03:25 +0800
Subject: [PATCH 047/449] [hotfix] fix a typo
---
docs/book/13-Functional-Programming.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md
index cf7b1920..ab7ec49b 100644
--- a/docs/book/13-Functional-Programming.md
+++ b/docs/book/13-Functional-Programming.md
@@ -377,7 +377,7 @@ Help!
**[8]** 这是一个**静态**方法引用。
-**[9]** 这是 **[6]** 的另一个版本:附加到存活对象的方法的方法参考,有时称为*绑定方法引用*。
+**[9]** 这是 **[6]** 的另一个版本:对已实例化对象的方法的引用,有时称为*绑定方法引用*。
**[10]** 最后,获取静态内部类的方法引用的操作与 **[8]** 中外部类方式一样。
From 83e28508afad8b95aee1e39702cf0959003be5e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 17:58:44 +0800
Subject: [PATCH 048/449] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E5=B0=8F=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/16-Validating-Your-Code.md | 196 ++++++++++++++++++++++++++-
1 file changed, 195 insertions(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 2b296acc..76c460e5 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -3,10 +3,204 @@
# 第十六章 代码校验
+##### 你永远不能保证你的代码是正确的,你只能证明它是错的。
+
+让我们先暂停学习编程语言的知识,看看一些代码基础知识。特别是能让你的代码更加健壮的。
## 测试
+如果没有测试过,它就是不能工作的。
+
+Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性过于感到舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则,但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试,争对你的目标检查代码行为。
+
+### 单元测试
+
+这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。
+
+“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查完成的程序是否满足要求。
+
+C风格的语言,特别是C++,通常会认为性能比安全更重要。用Java编程比C++(一般认为大概快两倍)快的原因是Java的安全性网络:这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中,你扩大了这个安全网,从而导致了更快的开发效率。当你发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码,并更快地生成更好的产品。
+
+当我意识到,要保证书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在我的系统发现了一个严重的漏洞. 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证证明书中所示的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,我一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
+
+### JUnit
+
+最初的Junit发布于2000年,大概是基于Java 1.0,因此不能使用Java的反射工具。因此,用旧的JUnit编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为注解一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,Junit通过反射和注解得到了极大的改进,这大大简化了编写单元测试代码的过程。在Java8中,他们甚至增加了对lambdas表达式的支持。本书使用当时最新的Junit5版本
+
+在JUnit最简单的使用中,使用**@Test**注解标记表示测试的每个方法。JUnit将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。
+
+让我们尝试一个简单的例子。**CountedLis**t继承**ArrayList**,添加信息来追踪有多少个**CountedLists**被创建:
+
+```java
+// validating/CountedList.java
+// Keeps track of how many of itself are created.
+package validating;
+import java.util.*;
+public class CountedList extends ArrayList {
+ private static int counter = 0;
+ private int id = counter++;
+ public CountedList() {
+ System.out.println("CountedList #" + id);
+ }
+ public int getId() { return id; }
+}
+```
+
+标准实例是将测试放在它们自己的子目录中。测试还必须放在包中,以便JUnit能够发现它们:
+
+```java
+// validating/tests/CountedListTest.java
+// Simple use of JUnit to test CountedList.
+package validating;
+import java.util.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+public class CountedListTest {
+private CountedList list;
+ @BeforeAll
+ static void beforeAllMsg() {
+ System.out.println(">>> Starting CountedListTest");
+ }
+
+ @AfterAll
+ static void afterAllMsg() {
+ System.out.println(">>> Finished CountedListTest");
+ }
+
+ @BeforeEach
+ public void initialize() {
+ list = new CountedList();
+ System.out.println("Set up for " + list.getId());
+ for(int i = 0; i < 3; i++)
+ list.add(Integer.toString(i));
+ }
+
+ @AfterEach
+ public void cleanup() {
+ System.out.println("Cleaning up " + list.getId());
+ }
+
+ @Test
+ public void insert() {
+ System.out.println("Running testInsert()");
+ assertEquals(list.size(), 3);
+ list.add(1, "Insert");
+ assertEquals(list.size(), 4);
+ assertEquals(list.get(1), "Insert");
+ }
+
+ @Test
+ public void replace() {
+ System.out.println("Running testReplace()");
+ assertEquals(list.size(), 3);
+ list.set(1, "Replace");
+ assertEquals(list.size(), 3);
+ assertEquals(list.get(1), "Replace");
+ }
+
+ // A helper method to simplify the code. As
+ // long as it's not annotated with @Test, it will
+ // not be automatically executed by JUnit.
+ private void compare(List lst, String[] strs) {
+ assertArrayEquals(lst.toArray(new String[0]), strs);
+ }
+
+ @Test
+ public void order() {
+ System.out.println("Running testOrder()");
+ compare(list, new String[] { "0", "1", "2" });
+ }
+
+ @Test
+ public void remove() {
+ System.out.println("Running testRemove()");
+ assertEquals(list.size(), 3);
+ list.remove(1);
+ assertEquals(list.size(), 2);
+ compare(list, new String[] { "0", "2" });
+ }
+
+ @Test
+ public void addAll() {
+ System.out.println("Running testAddAll()");
+ list.addAll(Arrays.asList(new String[] {
+ "An", "African", "Swallow"}));
+ assertEquals(list.size(), 6);
+ compare(list, new String[] { "0", "1", "2",
+ "An", "African", "Swallow" });
+ }
+}
+
+/* Output:
+>>> Starting CountedListTest
+CountedList #0
+Set up for 0
+Running testRemove()
+Cleaning up 0
+CountedList #1
+Set up for 1
+Running testReplace()
+Cleaning up 1
+CountedList #2
+Set up for 2
+Running testAddAll()
+Cleaning up 2
+CountedList #3
+Set up for 3
+Running testInsert()
+Cleaning up 3
+CountedList #4
+Set up for 4
+Running testOrder()
+Cleaning up 4
+>>> Finished CountedListTest
+*/
+```
+
+**@BeforeAll**注解是在任何其他测试操作之前运行一次的方法。**@AfterAll**是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。
+
+**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。或者,您可以将所有这样的初始化放在test类的构造函数中,尽管我认为**@BeforeEach**更加清晰。JUnit为每个测试创建一个对象,以确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用**@BeforeEach**和构造函数之间的唯一区别是**@BeforeEach**在测试前直接调用。在大多数情况下,这不是问题,如果您愿意,可以使用构造函数方法。
+
+如果您必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解**@AfterEach**.
+
+每个测试创建一个新的**CountedListTest**对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用**initialize()**,于是list被分配了一个新的**CountedList**对象,然后用**String“0”、“1”**和**“2”**初始化。观察**@BeforeEach**和**@AfterEach**的行为,这些方法在初始化和清理测试时显示有关测试的信息。
+
+**insert()**和**replace()**演示了典型的测试方法。JUnit使用**@Test**注解发现这些方法,并将每个方法作为测试运行。在方法内部,您可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。
+
+断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
+
+**compare()**是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test**注解,Junit就不会运行它,也不需要特定的签名。在这,**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为compare()方法来消除重复的代码。
+
+本书使用**build.gradle**控制测试,运行本章节的测试,命令:
+
+**gradlew validating:test**
+
+Gradle不运行已经运行过的测试,所以如果你没有得到测试结果,先运行:
+
+**gradlew validating:clean**
+
+你可以用这个命令运行本书的所有测试:
+
+**gradlew test**
+
+尽管你可以用最简单的方法,如CountedListTest.java所示
+
+JUnit包含许多额外的测试业务,您可以在其上了解这些结构
+
+[junit.org]: junit.org.
+
+Junit是Java最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合你的那一个。
+
+### 测试覆盖率的幻觉
+
+测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage)
+
+计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)
+
+对于没有知识但处于控制地位的人来说,确定100%的覆盖率是唯一可接受的值是非常容易的。这是一个问题,因为这个数字并不能很好地度量测试的有效性。您可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,您将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
+
+当您分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来告诉你关于测试质量的任何信息。
## 前提条件
@@ -61,4 +255,4 @@
-
\ No newline at end of file
+
From ff4e8d183a9945997adc40f4a3f410b16c98ce6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 17:59:40 +0800
Subject: [PATCH 049/449] add bold font
---
docs/book/16-Validating-Your-Code.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 76c460e5..d4e56dd7 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -10,7 +10,7 @@
## 测试
-如果没有测试过,它就是不能工作的。
+##### 如果没有测试过,它就是不能工作的。
Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性过于感到舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则,但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试,争对你的目标检查代码行为。
From 6460ea70ca4a45b379f3f5577cc40917b896103e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 18:01:44 +0800
Subject: [PATCH 050/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index d4e56dd7..04033f40 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -198,7 +198,7 @@ Junit是Java最流行的单元测试框架,但也有其它可以替代的。
计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)
-对于没有知识但处于控制地位的人来说,确定100%的覆盖率是唯一可接受的值是非常容易的。这是一个问题,因为这个数字并不能很好地度量测试的有效性。您可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,您将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
+对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这是一个问题,因为100%并不意味着是对测试有效性的最佳测量。您可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,您将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
当您分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来告诉你关于测试质量的任何信息。
From 8ee0a21aa3b81a30ab5d960df1779c286bf1675f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 18:04:17 +0800
Subject: [PATCH 051/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 31 ++++++++++++++--------------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 04033f40..0cd4466d 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -2,12 +2,12 @@
# 第十六章 代码校验
-
##### 你永远不能保证你的代码是正确的,你只能证明它是错的。
让我们先暂停学习编程语言的知识,看看一些代码基础知识。特别是能让你的代码更加健壮的。
-
+
+
## 测试
##### 如果没有测试过,它就是不能工作的。
@@ -203,56 +203,55 @@ Junit是Java最流行的单元测试框架,但也有其它可以替代的。
当您分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来告诉你关于测试质量的任何信息。
-## 前提条件
+## 前提条件
-## 测试驱动开发
+## 测试驱动开发
-## 日志
+## 日志
-## 调试
+## 调试
-## 基准测试
+## 基准测试
-## 分析和优化
+## 分析和优化
-## 风格检测
+## 风格检测
-## 静态错误分析
+## 静态错误分析
-## 代码重审
+## 代码重审
-## 结对编程
+## 结对编程
-## 重构
+## 重构
-## 持续集成
+## 持续集成
+
## 本章小结
-
-
From 8d077baa26d492f69f3edcf290bbfa1d65cd88bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 18:04:50 +0800
Subject: [PATCH 052/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 0cd4466d..8ef0a8e3 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -2,7 +2,7 @@
# 第十六章 代码校验
-##### 你永远不能保证你的代码是正确的,你只能证明它是错的。
+### 你永远不能保证你的代码是正确的,你只能证明它是错的。
让我们先暂停学习编程语言的知识,看看一些代码基础知识。特别是能让你的代码更加健壮的。
@@ -10,7 +10,7 @@
## 测试
-##### 如果没有测试过,它就是不能工作的。
+### 如果没有测试过,它就是不能工作的。
Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性过于感到舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则,但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试,争对你的目标检查代码行为。
From 9697ee398d1c999e0b0fe0b39979d716e0b9b8bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Mon, 26 Aug 2019 18:10:33 +0800
Subject: [PATCH 053/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 8ef0a8e3..cb786ccd 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -1,7 +1,11 @@
+
+
[TOC]
+
# 第十六章 代码校验
+
### 你永远不能保证你的代码是正确的,你只能证明它是错的。
让我们先暂停学习编程语言的知识,看看一些代码基础知识。特别是能让你的代码更加健壮的。
@@ -22,7 +26,7 @@ Java是一个静态类型的语言,程序员经常对一种编程语言明显
C风格的语言,特别是C++,通常会认为性能比安全更重要。用Java编程比C++(一般认为大概快两倍)快的原因是Java的安全性网络:这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中,你扩大了这个安全网,从而导致了更快的开发效率。当你发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码,并更快地生成更好的产品。
-当我意识到,要保证书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在我的系统发现了一个严重的漏洞. 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证证明书中所示的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,我一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
+当我意识到,要保证书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,我一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
### JUnit
From 1d83c6b24977c193a03813ba2dff06f10212b9c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 11:02:40 +0800
Subject: [PATCH 054/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 203 ++++++++++++++++++++++++++-
1 file changed, 202 insertions(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index cb786ccd..c226c410 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -208,7 +208,208 @@ Junit是Java最流行的单元测试框架,但也有其它可以替代的。
-## 前提条件
+## 前提条件。
+前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从Java的断言机制开始来介绍DBC,最后使用谷歌Guava库作为前置条件。
+
+#### 断言(Assertions)
+
+断言通过验证在程序执行期间满足某些条件而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段,它表示日历上的月份。这个数字总是介于1-12之间。通过断言检查,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。
+
+#### Java断言语法
+
+你可以通过其它程序设计架构来模拟断言的效果,因此,在Java中包含断言的意义在于它们易于编写。断言语句有两种形式 :
+
+assert boolean-expression;
+
+assert boolean-expression: information-expression;
+
+两者似乎告诉我们 **“我断言这个布尔表达式会产生一个真正的值”**, 否则,将抛出**AssertionError**异常。
+
+这是**Throwable**的派生类,因此不需要异常规范。
+
+不幸的是,第一种断言形式的异常不会生成包含布尔表达式的任何信息(与大多数其他语言的断言机制相反)。
+
+下面是第一种形式的例子:
+
+```java
+// validating/Assert1.java
+
+// Non-informative style of assert
+// Must run using -ea flag:
+// {java -ea Assert1}
+// {ThrowsException}
+public class Assert1 {
+ public static void main(String[] args) {
+ assert false;
+ }
+}
+
+/* Output:
+___[ Error Output ]___
+Exception in thread "main" java.lang.AssertionError
+at Assert1.main(Assert1.java:9)
+*/
+```
+
+如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag, 它也可以表示为: **-enableassertion**, 这将运行程序并执行任何断言语句。
+
+输出中并没有包含多少有用的信息。另一方面,如果你使用**information-expression**, 你将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的**information-expression**通常是一串针对程序员的文本:
+
+```java
+// validating/Assert2.java
+// Assert with an information-expression
+// {java Assert2 -ea}
+// {ThrowsException}
+
+public class Assert2 {
+ public static void main(String[] args) {
+ assert false:
+ "Here's a message saying what happened";
+ }
+}
+/* Output:
+___[ Error Output ]___
+Exception in thread "main" java.lang.AssertionError:
+Here's a message saying what happened
+at Assert2.main(Assert2.java:8)
+*/
+```
+
+**information-expression**可以产生任何类型的对象,因此,通常你将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
+
+还可以通过类名或包名打开或关闭断言;也就是说,您可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。您想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。然而,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。
+
+这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中**setDefaultAssertionStatus ()**,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言:
+
+```java
+// validating/LoaderAssertions.java
+// Using the class loader to enable assertions
+// {ThrowsException}
+public class LoaderAssertions {
+public static void main(String[] args) {
+
+ ClassLoader.getSystemClassLoader().
+ setDefaultAssertionStatus(true);
+ new Loaded().go();
+ }
+}
+
+class Loaded {
+ public void go() {
+ assert false: "Loaded.go()";
+ }
+}
+/* Output:
+___[ Error Output ]___
+Exception in thread "main" java.lang.AssertionError:
+Loaded.go()
+at Loaded.go(LoaderAssertions.java:15)
+at
+LoaderAssertions.main(LoaderAssertions.java:9)
+*/
+```
+
+这消除了在运行程序时在命令行上使用**-ea**标志的需要,使用**-ea**标志启用断言可能同样简单。当交付独立产品时,您可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的**static**块来实现这一点,该语句位于系统的主类中:
+
+```java
+static {
+ boolean assertionsEnabled = false;
+ // Note intentional side effect of assignment:
+ assert assertionsEnabled = true;
+ if(!assertionsEnabled)
+ throw new RuntimeException("Assertions disabled");
+}
+```
+
+
+
+如果启用断言,然后执行**assert**语句,**assertionsEnabled**变为**true**。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert**语句不执行,**assertionsEnabled**保持false,将导致异常。
+
+
+
+#### Guava断言
+
+因为启用Java本地断言很麻烦,Guava团队添加一个始终启用替换断言的**Verify**类。他们建议静态导入**Verify**方法:
+
+```java
+// validating/GuavaAssertions.java
+// Assertions that are always enabled.
+
+import com.google.common.base.*;
+import static com.google.common.base.Verify.*;
+public class GuavaAssertions {
+ public static void main(String[] args) {
+ verify(2 + 2 == 4);
+ try {
+ verify(1 + 2 == 4);
+ } catch(VerifyException e) {
+ System.out.println(e);
+ }
+
+ try {
+ verify(1 + 2 == 4, "Bad math");
+ } catch(VerifyException e) {
+ System.out.println(e.getMessage());
+ }
+
+ try {
+ verify(1 + 2 == 4, "Bad math: %s", "not 4");
+ } catch(VerifyException e) {
+ System.out.println(e.getMessage());
+ }
+
+ String s = "";
+ s = verifyNotNull(s);
+ s = null;
+ try {
+ verifyNotNull(s);
+ } catch(VerifyException e) {
+ System.out.println(e.getMessage());
+ }
+
+ try {
+ verifyNotNull(
+ s, "Shouldn't be null: %s", "arg s");
+ } catch(VerifyException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+}
+/* Output:
+com.google.common.base.VerifyException
+Bad math
+Bad math: not 4
+expected a non-null reference
+Shouldn't be null: arg s
+*/
+```
+
+
+
+这里有两个方法,使用变量**verify()**和**verifyNotNull()**来支持有用的错误消息。注意,**verifyNotNull()**内置的错误消息通常就足够了,而**verify()**太一般,没有有用的默认错误消息。
+
+
+
+#### 使用断言进行契约式设计
+
+*契约式设计(DbC)*是Bertrand Meyer提出的一个概念,Eiffel语言的发明者,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现**DBC**(Eiffel也是如此),但是它们创建了一种非正式的DBC编程风格。DbC假定服务供应商与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应商和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。
+
+**Meyer**认为:
+
+1.应该明确指定行为,就好像它是一个契约一样。
+
+2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。
+
+不管你是否同意,第一条总是对的,在足够多的情况下,DbC确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类DbC约束的表达式;如果无法指定约束,则可能对要构建的内容了解得不够。
+
+#### check指令
+
+详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为check指令。check指令说明你确信代码中的某个特定属性此时已经得到满足。check指令的思想是用表达代码中非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
+
+在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的check指令是能够断定液体变蓝了。
+
+check指令对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
+
From 3eac1b76993fec452b2ae1d538c0d8fca186199d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 11:04:24 +0800
Subject: [PATCH 055/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index c226c410..d9a0fc20 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -404,7 +404,7 @@ Shouldn't be null: arg s
#### check指令
-详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为check指令。check指令说明你确信代码中的某个特定属性此时已经得到满足。check指令的思想是用表达代码中非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
+详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为check指令。check指令说明你确信代码中的某个特定属性此时已经得到满足。check指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的check指令是能够断定液体变蓝了。
From 27f748fd892272711a5ce905042f5144c8a9996a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 15:19:51 +0800
Subject: [PATCH 056/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 64 ++++++++++++++++++++++------
1 file changed, 52 insertions(+), 12 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index d9a0fc20..ded483fd 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -1,5 +1,3 @@
-
-
[TOC]
@@ -18,7 +16,7 @@
Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性过于感到舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则,但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试,争对你的目标检查代码行为。
-### 单元测试
+#### 单元测试
这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。
@@ -28,7 +26,7 @@ C风格的语言,特别是C++,通常会认为性能比安全更重要。用J
当我意识到,要保证书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,我一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
-### JUnit
+#### JUnit
最初的Junit发布于2000年,大概是基于Java 1.0,因此不能使用Java的反射工具。因此,用旧的JUnit编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为注解一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,Junit通过反射和注解得到了极大的改进,这大大简化了编写单元测试代码的过程。在Java8中,他们甚至增加了对lambdas表达式的支持。本书使用当时最新的Junit5版本
@@ -174,7 +172,7 @@ Cleaning up 4
断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
-**compare()**是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test**注解,Junit就不会运行它,也不需要特定的签名。在这,**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为compare()方法来消除重复的代码。
+**compare()**是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test**注解,Junit就不会运行它,也不需要特定的签名。在这,**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为**compare()**方法来消除重复的代码。
本书使用**build.gradle**控制测试,运行本章节的测试,命令:
@@ -196,7 +194,7 @@ JUnit包含许多额外的测试业务,您可以在其上了解这些结构
Junit是Java最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合你的那一个。
-### 测试覆盖率的幻觉
+#### 测试覆盖率的幻觉
测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage)
@@ -208,7 +206,8 @@ Junit是Java最流行的单元测试框架,但也有其它可以替代的。
-## 前提条件。
+## 前置条件
+
前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从Java的断言机制开始来介绍DBC,最后使用谷歌Guava库作为前置条件。
#### 断言(Assertions)
@@ -402,16 +401,57 @@ Shouldn't be null: arg s
不管你是否同意,第一条总是对的,在足够多的情况下,DbC确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类DbC约束的表达式;如果无法指定约束,则可能对要构建的内容了解得不够。
-#### check指令
+#### 检查指令
+
+详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
+
+在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。
+
+检查指令是对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
+
+#### 前置条件
+
+前置条件确保客户端(调用此方法的代码)履行其部分契约。这意味着在方法调用开始时几乎总是会检查参数(在你用那个方法做任何操作之前)以此保证它们的调用在方法中是合适的。因为你永远无法知道客户端会传递给你什么,前置条件是确保检查的一个好做法。
+
+#### 后置条件
+
+后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在**return**语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。
+
+#### 不变性
+
+不变性保证了必须在方法调用之间维护的对象的状态。但是,它并不会阻止方法在执行过程中暂时偏离这些保证,它只是在说对象的状态信息应该总是遵守状态规则:
+
+**1**. 在进入该方法时。
+
+**2**. 在离开方法之前。
+
+此外,不变性是关于构造后对象状态的保证。
+
+根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为**invariant()**,它在构造之后以及每个方法的开始和结束时调用。方法可调用如下:
+
+assert invariant();
+
+这样,如果出于性能原因禁用断言,就不会产生开销。
+
+#### 放松DBC检查 或 非严格的DBC
+
+尽管他强调了前置条件、后置条件和不变性的价值所在,以及在开发过程中使用它们的重要性,Meyer承认在一个产品中包含所有DbC代码并不总是实用的。您可以放松DbC检查,它基于在特定的地方,你可以对代码的信任程度。以下是放松检查的顺序,最安全到最不安全:
+
+**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。
+
+**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是如果你对代码有信心的话。
+
+**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用**invariant()**那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
-详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为check指令。check指令说明你确信代码中的某个特定属性此时已经得到满足。check指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。
+**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A)**在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B) **并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
-在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的check指令是能够断定液体变蓝了。
+不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,你可以轻松地恢复检查以快速发现问题。
-check指令对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
+#### DBC + 单元测试
+####
-
+。
## 测试驱动开发
From a2a54aab978c123c8207712dead26fbe22872943 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 15:24:54 +0800
Subject: [PATCH 057/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 40 ++++++++++++++--------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index ded483fd..af76d23e 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -30,9 +30,9 @@ C风格的语言,特别是C++,通常会认为性能比安全更重要。用J
最初的Junit发布于2000年,大概是基于Java 1.0,因此不能使用Java的反射工具。因此,用旧的JUnit编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为注解一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,Junit通过反射和注解得到了极大的改进,这大大简化了编写单元测试代码的过程。在Java8中,他们甚至增加了对lambdas表达式的支持。本书使用当时最新的Junit5版本
-在JUnit最简单的使用中,使用**@Test**注解标记表示测试的每个方法。JUnit将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。
+在JUnit最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。
-让我们尝试一个简单的例子。**CountedLis**t继承**ArrayList**,添加信息来追踪有多少个**CountedLists**被创建:
+让我们尝试一个简单的例子。**CountedList** 继承 **ArrayList** ,添加信息来追踪有多少个**CountedLists**被创建:
```java
// validating/CountedList.java
@@ -160,19 +160,19 @@ Cleaning up 4
*/
```
-**@BeforeAll**注解是在任何其他测试操作之前运行一次的方法。**@AfterAll**是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。
+**@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。
-**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。或者,您可以将所有这样的初始化放在test类的构造函数中,尽管我认为**@BeforeEach**更加清晰。JUnit为每个测试创建一个对象,以确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用**@BeforeEach**和构造函数之间的唯一区别是**@BeforeEach**在测试前直接调用。在大多数情况下,这不是问题,如果您愿意,可以使用构造函数方法。
+**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。或者,您可以将所有这样的初始化放在test类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,以确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果您愿意,可以使用构造函数方法。
-如果您必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解**@AfterEach**.
+如果您必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**.
-每个测试创建一个新的**CountedListTest**对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用**initialize()**,于是list被分配了一个新的**CountedList**对象,然后用**String“0”、“1”**和**“2”**初始化。观察**@BeforeEach**和**@AfterEach**的行为,这些方法在初始化和清理测试时显示有关测试的信息。
+每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是list被分配了一个新的 **CountedList** 对象,然后用 **String“0”、“1”** 和 **“2”** 初始化。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。
-**insert()**和**replace()**演示了典型的测试方法。JUnit使用**@Test**注解发现这些方法,并将每个方法作为测试运行。在方法内部,您可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。
+**insert()** 和 **replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,您可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。
断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
-**compare()**是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test**注解,Junit就不会运行它,也不需要特定的签名。在这,**compare()**是**private**,表示在测试类使用,但他同样可以是**public**。其余的测试方法通过将其重构为**compare()**方法来消除重复的代码。
+**compare()** 是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test** 注解,Junit就不会运行它,也不需要特定的签名。在这, **compare()** 是 **private** ,表示在测试类使用,但他同样可以是 **public** 。其余的测试方法通过将其重构为 **compare()** 方法来消除重复的代码。
本书使用**build.gradle**控制测试,运行本章节的测试,命令:
@@ -250,9 +250,9 @@ at Assert1.main(Assert1.java:9)
*/
```
-如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag, 它也可以表示为: **-enableassertion**, 这将运行程序并执行任何断言语句。
+如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag, 它也可以表示为: **-enableassertion** , 这将运行程序并执行任何断言语句。
-输出中并没有包含多少有用的信息。另一方面,如果你使用**information-expression**, 你将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的**information-expression**通常是一串针对程序员的文本:
+输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 你将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:
```java
// validating/Assert2.java
@@ -274,11 +274,11 @@ at Assert2.main(Assert2.java:8)
*/
```
-**information-expression**可以产生任何类型的对象,因此,通常你将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
+**information-expression** 可以产生任何类型的对象,因此,通常你将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
还可以通过类名或包名打开或关闭断言;也就是说,您可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。您想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。然而,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。
-这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中**setDefaultAssertionStatus ()**,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言:
+这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言:
```java
// validating/LoaderAssertions.java
@@ -308,7 +308,7 @@ LoaderAssertions.main(LoaderAssertions.java:9)
*/
```
-这消除了在运行程序时在命令行上使用**-ea**标志的需要,使用**-ea**标志启用断言可能同样简单。当交付独立产品时,您可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的**static**块来实现这一点,该语句位于系统的主类中:
+这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,您可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:
```java
static {
@@ -322,13 +322,13 @@ static {
-如果启用断言,然后执行**assert**语句,**assertionsEnabled**变为**true**。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert**语句不执行,**assertionsEnabled**保持false,将导致异常。
+如果启用断言,然后执行 **assert** 语句,**assertionsEnabled** 变为 **true** 。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert** 语句不执行,**assertionsEnabled** 保持false,将导致异常。
#### Guava断言
-因为启用Java本地断言很麻烦,Guava团队添加一个始终启用替换断言的**Verify**类。他们建议静态导入**Verify**方法:
+因为启用Java本地断言很麻烦,Guava团队添加一个始终启用替换断言的 **Verify** 类。他们建议静态导入 **Verify** 方法:
```java
// validating/GuavaAssertions.java
@@ -385,13 +385,13 @@ Shouldn't be null: arg s
-这里有两个方法,使用变量**verify()**和**verifyNotNull()**来支持有用的错误消息。注意,**verifyNotNull()**内置的错误消息通常就足够了,而**verify()**太一般,没有有用的默认错误消息。
+这里有两个方法,使用变量 **verify()** 和 **verifyNotNull()** 来支持有用的错误消息。注意,**verifyNotNull()** 内置的错误消息通常就足够了,而 **verify()** 太一般,没有有用的默认错误消息。
#### 使用断言进行契约式设计
-*契约式设计(DbC)*是Bertrand Meyer提出的一个概念,Eiffel语言的发明者,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现**DBC**(Eiffel也是如此),但是它们创建了一种非正式的DBC编程风格。DbC假定服务供应商与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应商和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。
+*契约式设计(DbC)*是Bertrand Meyer提出的一个概念,Eiffel语言的发明者,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel也是如此),但是它们创建了一种非正式的DBC编程风格。DbC假定服务供应商与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应商和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。
**Meyer**认为:
@@ -427,7 +427,7 @@ Shouldn't be null: arg s
此外,不变性是关于构造后对象状态的保证。
-根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为**invariant()**,它在构造之后以及每个方法的开始和结束时调用。方法可调用如下:
+根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 **invariant()** ,它在构造之后以及每个方法的开始和结束时调用。方法可调用如下:
assert invariant();
@@ -441,9 +441,9 @@ assert invariant();
**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是如果你对代码有信心的话。
-**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用**invariant()**那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
+**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
-**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A)**在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B) **并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
+**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,你可以轻松地恢复检查以快速发现问题。
From c8fcee9d1f398675799be57a0cf0f02f9214f8c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 15:26:10 +0800
Subject: [PATCH 058/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index af76d23e..c3bb20b6 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -443,7 +443,7 @@ assert invariant();
**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
-**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
+**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A)** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,你可以轻松地恢复检查以快速发现问题。
From 585176b6399dd9bd694656bfc351396e65058c06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 16:48:04 +0800
Subject: [PATCH 059/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 462 ++++++++++++++++++++++++++-
1 file changed, 461 insertions(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index c3bb20b6..3e6c72d0 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -449,7 +449,467 @@ assert invariant();
#### DBC + 单元测试
-####
+下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,类将回绕到开头。
+
+我们可以对这个队列做一些契约定义:
+
+**1**. 前置条件(用于put()):不允许将空元素添加到队列中。
+
+**2**. 前置条件(用于put()):将元素放入完整队列是非法的。
+
+**3**. 前置条件(用于get()):试图从空队列中获取元素是非法的。
+
+**4**. 后置条件用于get()):不能从数组中生成空元素。
+
+**5**. 不变性:包含对象的区域不能包含任何空元素。
+
+**6**. 不变性:不包含对象的区域必须只有空值。
+
+下面是实现这些规则的一种方法,为每个DbC元素类型使用显式方法调用。首先,我们创建一个专用的
+
+##### Exception:
+
+- ```java
+ // validating/CircularQueueException.java
+ package validating;
+ public class CircularQueueException extends RuntimeException {
+ public CircularQueueException(String why) {
+ super(why);
+ }
+ }
+
+ This is used to report errors with the CircularQueue class:
+ // validating/CircularQueue.java
+ // Demonstration of Design by Contract (DbC)
+ package validating;
+ import java.util.*;
+ public class CircularQueue {
+ private Object[] data;
+ private int in = 0, // Next available storage space
+ out = 0; // Next gettable object
+ // Has it wrapped around the circular queue?
+ private boolean wrapped = false;
+ public CircularQueue(int size) {
+ data = new Object[size];
+ // Must be true after construction:
+ assert invariant();
+ }
+
+ public boolean empty() {
+ return !wrapped && in == out;
+ }
+
+ public boolean full() {
+ return wrapped && in == out;
+ }
+
+ public boolean isWrapped() { return wrapped; }
+
+ public void put(Object item) {
+ precondition(item != null, "put() null item");
+ precondition(!full(),
+ "put() into full CircularQueue");
+ assert invariant();
+ data[in++] = item;
+ if(in >= data.length) {
+ in = 0;
+ wrapped = true;
+ }
+ assert invariant();
+ }
+
+ public Object get() {
+ precondition(!empty(),
+ "get() from empty CircularQueue");
+ assert invariant();
+ Object returnVal = data[out];
+ data[out] = null;
+ out++;
+ if(out >= data.length) {
+ out = 0;
+ wrapped = false;
+ }
+ assert postcondition(
+ returnVal != null,
+ "Null item in CircularQueue");
+ assert invariant();
+ return returnVal;
+ }
+
+ // Design-by-contract support methods:
+ private static void precondition(boolean cond, String msg) {
+ if(!cond) throw new CircularQueueException(msg);
+ }
+
+ private static boolean postcondition(boolean cond, String msg) {
+ if(!cond) throw new CircularQueueException(msg);
+ return true;
+ }
+
+ private boolean invariant() {
+ // Guarantee that no null values are in the
+ // region of 'data' that holds objects:
+ for(int i = out; i != in; i = (i + 1) % data.length)
+ if(data[i] == null)
+ throw new CircularQueueException(
+ "null in CircularQueue");
+ // Guarantee that only null values are outside the
+ // region of 'data' that holds objects:
+ if(full()) return true;
+ for(int i = in; i != out; i = (i + 1) % data.length)
+ if(data[i] != null)
+ throw new CircularQueueException(
+ "non-null outside of CircularQueue range: "
+ dump());
+ return true;
+ }
+
+ public String dump() {
+ return "in = " + in +
+ ", out = " + out +
+ ", full() = " + full() +
+ ", empty() = " + empty() +
+ ", CircularQueue = " + Arrays.asList(data);
+ }
+ }
+ ```
+
+ **in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。
+
+ **put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。
+
+
+
+ 注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。
+
+
+
+ **postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果您对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果您的单元测试是足够的,那么你应当对不变性保持一定的信心。
+
+
+
+ **dump()** helper方法返回一个包含所有数据的字符串,而不是直接打印数据。这表示你可以展示更多的信息。
+
+
+
+ 现在我们可以为类创建JUnit测试:
+
+ ```java
+ // validating/tests/CircularQueueTest.java
+ package validating;
+ import org.junit.jupiter.api.*;
+ import static org.junit.jupiter.api.Assertions.*;
+ public class CircularQueueTest {
+ private CircularQueue queue = new CircularQueue(10);
+ private int i = 0;
+
+ @BeforeEach
+ public void initialize() {
+ while(i < 5) // Pre-load with some data
+ queue.put(Integer.toString(i++));
+ }
+
+ // Support methods:
+ private void showFullness() {
+ assertTrue(queue.full());
+ assertFalse(queue.empty());
+ System.out.println(queue.dump());
+ }
+
+ private void showEmptiness() {
+ assertFalse(queue.full());
+ assertTrue(queue.empty());
+ System.out.println(queue.dump());
+ }
+
+ @Test
+ public void full() {
+ System.out.println("testFull");
+ System.out.println(queue.dump());
+ System.out.println(queue.get());
+ System.out.println(queue.get());
+ while(!queue.full())
+ queue.put(Integer.toString(i++));
+ String msg = "";
+ try {
+ queue.put("");
+ } catch(CircularQueueException e) {
+ msg = e.getMessage();
+ System.out.println(msg);
+ }
+ assertEquals(msg, "put() into full CircularQueue");
+ showFullness();
+ }
+
+ @Test
+ public void empty() {
+ System.out.println("testEmpty");
+ while(!queue.empty())
+ System.out.println(queue.get());
+ String msg = "";
+ try {
+ queue.get();
+ } catch(CircularQueueException e) {
+ msg = e.getMessage();
+ System.out.println(msg);
+ }
+ assertEquals(msg, "get() from empty CircularQueue");
+ showEmptiness();
+ }
+ @Test
+ public void nullPut() {
+ System.out.println("testNullPut");
+ String msg = "";
+ try {
+ queue.put(null);
+ } catch(CircularQueueException e) {
+ msg = e.getMessage();
+ System.out.println(msg);
+ }
+ assertEquals(msg, "put() null item");
+ }
+
+ @Test
+ public void circularity() {
+ System.out.println("testCircularity");
+ while(!queue.full())
+ queue.put(Integer.toString(i++));
+ showFullness();
+ assertTrue(queue.isWrapped());
+
+ while(!queue.empty())
+ System.out.println(queue.get());
+ showEmptiness();
+
+ while(!queue.full())
+ queue.put(Integer.toString(i++));
+ showFullness();
+
+ while(!queue.empty())
+ System.out.println(queue.get());
+ showEmptiness();
+ }
+ }
+ /* Output:
+ testNullPut
+ put() null item
+ testCircularity
+ in = 0, out = 0, full() = true, empty() = false,
+ CircularQueue =
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ in = 0, out = 0, full() = false, empty() = true,
+ CircularQueue =
+ [null, null, null, null, null, null, null, null, null,
+ null]
+ in = 0, out = 0, full() = true, empty() = false,
+ CircularQueue =
+ [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ in = 0, out = 0, full() = false, empty() = true,
+ CircularQueue =
+ [null, null, null, null, null, null, null, null, null,
+ null]
+ testFull
+ in = 5, out = 0, full() = false, empty() = false,
+ CircularQueue =
+ [0, 1, 2, 3, 4, null, null, null, null, null]
+ 0
+ 1
+ put() into full CircularQueue
+ in = 2, out = 2, full() = true, empty() = false,
+ CircularQueue =
+ [10, 11, 2, 3, 4, 5, 6, 7, 8, 9]
+ testEmpty
+ 0
+ 1
+ 2
+ 3
+ 4
+ get() from empty CircularQueue
+ in = 5, out = 5, full() = false, empty() = true,
+ CircularQueue =
+ [null, null, null, null, null, null, null, null, null,
+ null]
+ */
+ ```
+
+ **initialize()** 添加了一些数据,因此每个测试的 **CircularQueue** 都是部分满的。**showFullness()** 和 **showempty()** 表明 **CircularQueue** 是满的还是空的,这四种测试方法中的每一种都确保了**CircularQueue**功能在不同地方的正确运行。
+
+通过将Dbc和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些Dbc测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。
+
+#### 使用Guava前置条件
+
+在非严格DBC中, 我指出,前置条件是DbC中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为Java在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。
+
+谷歌的Guava库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里您可以看到它们的简单用法。库的设计人员建议你静态导入前置条件:
+
+```java
+// validating/GuavaPreconditions.java
+// Demonstrating Guava Preconditions
+import java.util.function.*;
+import static com.google.common.base.Preconditions.*;
+public class GuavaPreconditions {
+ static void test(Consumer c, String s) {
+ try {
+ System.out.println(s);
+ c.accept(s);
+ System.out.println("Success");
+ } catch(Exception e) {
+ String type = e.getClass().getSimpleName();
+ String msg = e.getMessage();
+ System.out.println(type +
+ (msg == null ? "" : ": " + msg));
+ }
+ }
+
+ public static void main(String[] args) {
+ test(s -> s = checkNotNull(s), "X");
+ test(s -> s = checkNotNull(s), null);
+ test(s -> s = checkNotNull(s, "s was null"), null);
+ test(s -> s = checkNotNull(
+ s, "s was null, %s %s", "arg2", "arg3"), null);
+ test(s -> checkArgument(s == "Fozzie"), "Fozzie");
+ test(s -> checkArgument(s == "Fozzie"), "X");
+ test(s -> checkArgument(s == "Fozzie"), null);
+ test(s -> checkArgument(
+ s == "Fozzie", "Bear Left!"), null);
+ test(s -> checkArgument(
+ s == "Fozzie", "Bear Left! %s Right!", "Frog"),
+ null);
+ test(s -> checkState(s.length() > 6), "Mortimer");
+ test(s -> checkState(s.length() > 6), "Mort");
+ test(s -> checkState(s.length() > 6), null);
+ test(s ->
+ checkElementIndex(6, s.length()), "Robert");
+ test(s ->
+ checkElementIndex(6, s.length()), "Bob");
+ test(s ->
+ checkElementIndex(6, s.length()), null);
+ test(s ->
+ checkPositionIndex(6, s.length()), "Robert");
+ test(s ->
+ checkPositionIndex(6, s.length()), "Bob");
+ test(s ->
+ checkPositionIndex(6, s.length()), null);
+ test(s -> checkPositionIndexes(
+ 0, 6, s.length()), "Hieronymus");
+ test(s -> checkPositionIndexes(
+ 0, 10, s.length()), "Hieronymus");
+ test(s -> checkPositionIndexes(
+ 0, 11, s.length()), "Hieronymus");
+ test(s -> checkPositionIndexes(
+ -1, 6, s.length()), "Hieronymus");
+ test(s -> checkPositionIndexes(
+ 7, 6, s.length()), "Hieronymus");
+ test(s -> checkPositionIndexes(
+ 0, 6, s.length()), null);
+ }
+}
+/* Output:
+X
+Success
+null
+NullPointerException
+null
+NullPointerException: s was null
+null
+NullPointerException: s was null, arg2 arg3
+Fozzie
+Success
+X
+IllegalArgumentException
+null
+IllegalArgumentException
+null
+IllegalArgumentException: Bear Left!
+null
+IllegalArgumentException: Bear Left! Frog Right!
+Mortimer
+Success
+Mort
+IllegalStateException
+null
+NullPointerException
+Robert
+IndexOutOfBoundsException: index (6) must be less than
+size (6)
+Bob
+IndexOutOfBoundsException: index (6) must be less than
+size (3)
+null
+NullPointerException
+Robert
+Success
+Bob
+IndexOutOfBoundsException: index (6) must not be
+greater than size (3)
+null
+NullPointerException
+Hieronymus
+Success
+Hieronymus
+Success
+Hieronymus
+IndexOutOfBoundsException: end index (11) must not be
+greater than size (10)
+Hieronymus
+IndexOutOfBoundsException: start index (-1) must not be
+negative
+Hieronymus
+IndexOutOfBoundsException: end index (6) must not be
+less than start index (7)
+null
+NullPointerException
+*/
+```
+
+虽然Guava的前置条件适用于所有类型,但我只演示 字符串。类型。**test()**方法需要一个**Consumer**,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
+
+每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以您可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null **值的对象构造:
+
+/
+
+```java
+/ validating/NonNullConstruction.java
+import static com.google.common.base.Preconditions.*;
+public class NonNullConstruction {
+ private Integer n;
+ private String s;
+ NonNullConstruction(Integer n, String s) {
+ this.n = checkNotNull(n);
+ this.s = checkNotNull(s);
+ }
+ public static void main(String[] args) {
+ NonNullConstruction nnc =
+ new NonNullConstruction(3, "Trousers");
+ }
+}
+```
+
+**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()**用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。
+
+最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()**确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。**checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。
+
+所有Guava前置条件对于基本类型和对象都有必要的重载。
。
From 8f83875ddfcd43746aa25e1575afcb49a30e85c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 16:50:08 +0800
Subject: [PATCH 060/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 3e6c72d0..0bdf6eb0 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -882,7 +882,7 @@ NullPointerException
*/
```
-虽然Guava的前置条件适用于所有类型,但我只演示 字符串。类型。**test()**方法需要一个**Consumer**,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
+虽然Guava的前置条件适用于所有类型,但我只演示 **字符串(String)** 类型。**test()**方法需要一个**Consumer**,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以您可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null **值的对象构造:
From 0c70b057288ca49d41b9ce4f0dec61b758b60d11 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Tue, 27 Aug 2019 16:51:11 +0800
Subject: [PATCH 061/449] Update 16-Validating-Your-Code.md
---
docs/book/16-Validating-Your-Code.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 0bdf6eb0..e0cc5703 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -905,13 +905,13 @@ public class NonNullConstruction {
}
```
-**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()**用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。
+**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()** 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。
-最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()**确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。**checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。
+最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()** 确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。 **checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。
所有Guava前置条件对于基本类型和对象都有必要的重载。
-。
+
## 测试驱动开发
From 0c0054f40df267862e2cfbdc016a362e5e739a4d Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Tue, 27 Aug 2019 19:32:37 +0800
Subject: [PATCH 062/449] =?UTF-8?q?revision[14]=20=E5=9C=A8map()=E4=B8=AD?=
=?UTF-8?q?=E7=BB=84=E5=90=88=E6=B5=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 6924bd4b..7d4340fc 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -445,7 +445,7 @@ YNZBRNYGCFOWZNTCQRGSEGZMMJMROE
使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围,所以使用数组边界是经过深思熟虑的。
-如果要创建包含相同对象的流,只需要传递一个生成那些对象 `lambda` 到 `generate()` 中:
+如果要创建包含相同对象的流,只需要传递一个生成那些对象的 `lambda` 到 `generate()` 中:
```java
// streams/Duplicator.java
@@ -515,7 +515,7 @@ Bubble(3)
Bubble(4)
```
-这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这对于代码组织和品味是个问题——你总是可以创建一个完全不同的工厂类。
+这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这是一个对于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。
### iterate()
@@ -579,7 +579,7 @@ public class FileToWordsBuilder {
public FileToWordsBuilder(String filePath) throws Exception {
Files.lines(Paths.get(filePath))
.skip(1) // 略过开头的注释行
- .forEach(line -> {
+ .forEach(line -> {
for (String w : line.split("[ .?,]+"))
builder.add(w);
});
@@ -681,7 +681,7 @@ public class ArrayStreams {
### 正则表达式
-Java 的正则表达式已经在[字符串](18-Strings.md)这一章节介绍过了。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 **CharSequence**,因此不能将流作为 `splitAsStream()` 的参数。
+Java 的正则表达式将在[字符串](18-Strings.md)这一章节详细介绍。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 **CharSequence**,因此不能将流作为 `splitAsStream()` 的参数。
我们再一次查看将文件处理为单词流的过程。这一次,我们使用流将文件分割为单独的字符串,接着使用正则表达式将字符串转化为单词流。
@@ -765,7 +765,7 @@ Well WELL well it IT it s S s so SO so
`FileToWords` 稍后定义,但它的功能实现貌似和之前我们看到的差不多:产生字符串对象的流。之后在其通过管道时调用 `peek()` 进行处理。
-因为 `peek()` 符合无返回值的 **Consumer** 函数式接口。我们只能观察,无法使用不同的元素来替换流中的对象。
+因为 `peek()` 符合无返回值的 **Consumer** 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。
### 流元素排序
From 3b6981504ca4b68869b5299611694df1da6edfe2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8B=AC=E5=A5=8F=E7=8C=AB?=
<8319743+Leodegrance@users.noreply.github.com>
Date: Wed, 28 Aug 2019 10:38:51 +0800
Subject: [PATCH 063/449] =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=88=A0?=
=?UTF-8?q?=E9=99=A4=E6=97=A0=E7=94=A8=E5=8F=A5=E5=AD=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/16-Validating-Your-Code.md | 60 ++++++++++++++--------------
1 file changed, 30 insertions(+), 30 deletions(-)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index e0cc5703..07b27494 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -20,11 +20,11 @@ Java是一个静态类型的语言,程序员经常对一种编程语言明显
这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。
-“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查完成的程序是否满足要求。
+“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查程序是否满足要求。
-C风格的语言,特别是C++,通常会认为性能比安全更重要。用Java编程比C++(一般认为大概快两倍)快的原因是Java的安全性网络:这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中,你扩大了这个安全网,从而导致了更快的开发效率。当你发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码,并更快地生成更好的产品。
+C风格的语言,特别是C++,通常会认为性能比安全更重要。用Java编程比C++(一般认为大概快两倍)快的原因是Java的安全性网络:这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中,你扩大了这个安全网,有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码。
-当我意识到,要保证书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 并且我发现我可以利用搜索和替换在整本书大范围的修改。如果我引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。然而,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,我一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
+当我意识到,要确保书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 我发现可以利用搜索和替换在整本书大范围的修改。如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。尽管,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。
#### JUnit
@@ -162,13 +162,13 @@ Cleaning up 4
**@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。
-**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。或者,您可以将所有这样的初始化放在test类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,以确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果您愿意,可以使用构造函数方法。
+**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在test类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。
-如果您必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**.
+如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**.
每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是list被分配了一个新的 **CountedList** 对象,然后用 **String“0”、“1”** 和 **“2”** 初始化。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。
-**insert()** 和 **replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,您可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。
+**insert()** 和 **replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。
断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。
@@ -178,21 +178,21 @@ Cleaning up 4
**gradlew validating:test**
-Gradle不运行已经运行过的测试,所以如果你没有得到测试结果,先运行:
+Gradle不会运行已经运行过的测试,所以如果你没有得到测试结果,先运行:
**gradlew validating:clean**
-你可以用这个命令运行本书的所有测试:
+可以用这个命令运行本书的所有测试:
**gradlew test**
-尽管你可以用最简单的方法,如CountedListTest.java所示
+尽管可以用最简单的方法,如CountedListTest.java所示
-JUnit包含许多额外的测试业务,您可以在其上了解这些结构
+JUnit包含许多额外的测试业务,你可以在其上了解这些结构
[junit.org]: junit.org.
-Junit是Java最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合你的那一个。
+Junit是Java最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。
#### 测试覆盖率的幻觉
@@ -200,9 +200,9 @@ Junit是Java最流行的单元测试框架,但也有其它可以替代的。
计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)
-对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这是一个问题,因为100%并不意味着是对测试有效性的最佳测量。您可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,您将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
+对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这有一个问题,因为100%并不意味着是对测试有效性的最佳测量。你可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。
-当您分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来告诉你关于测试质量的任何信息。
+当分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来得到测试质量的任何信息。
@@ -252,7 +252,7 @@ at Assert1.main(Assert1.java:9)
如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag, 它也可以表示为: **-enableassertion** , 这将运行程序并执行任何断言语句。
-输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 你将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:
+输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:
```java
// validating/Assert2.java
@@ -274,9 +274,9 @@ at Assert2.main(Assert2.java:8)
*/
```
-**information-expression** 可以产生任何类型的对象,因此,通常你将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
+**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。
-还可以通过类名或包名打开或关闭断言;也就是说,您可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。您想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。然而,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。
+还可以通过类名或包名打开或关闭断言;也就是说,你可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。你想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。但是,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。
这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言:
@@ -308,7 +308,7 @@ LoaderAssertions.main(LoaderAssertions.java:9)
*/
```
-这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,您可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:
+这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:
```java
static {
@@ -399,7 +399,7 @@ Shouldn't be null: arg s
2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。
-不管你是否同意,第一条总是对的,在足够多的情况下,DbC确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类DbC约束的表达式;如果无法指定约束,则可能对要构建的内容了解得不够。
+不管你是否同意,第一条总是对的,在大多数情况下,DbC确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类DbC约束的表达式;如果无法指定约束,则可能对要构建的内容了解得不够。
#### 检查指令
@@ -407,7 +407,7 @@ Shouldn't be null: arg s
在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。
-检查指令是对你的代码进行补充,当您可以测试并阐明对象或程序的状态时,应该使用它。
+检查指令是对你的代码进行补充,当你可以测试并阐明对象或程序的状态时,应该使用它。
#### 前置条件
@@ -435,21 +435,21 @@ assert invariant();
#### 放松DBC检查 或 非严格的DBC
-尽管他强调了前置条件、后置条件和不变性的价值所在,以及在开发过程中使用它们的重要性,Meyer承认在一个产品中包含所有DbC代码并不总是实用的。您可以放松DbC检查,它基于在特定的地方,你可以对代码的信任程度。以下是放松检查的顺序,最安全到最不安全:
+尽管他强调了前置条件、后置条件和不变性的价值所在,以及在开发过程中使用它们的重要性,Meyer承认在一个产品中包含所有DbC代码并不总是实用的。你可以放松DbC检查,它基于在特定的地方,你可以对代码的信任程度。以下是放松检查的顺序,最安全到最不安全:
**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。
-**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是如果你对代码有信心的话。
+**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是当对自己的代码有信心时。
-**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管,它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
+**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。
-**4**. 最后,万不得已,禁用前置条件检查。这是最不安全、最不明智的选择,因为尽管你知道并且可以控制你自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,**(A)** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
+**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。**(A)** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。
-不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,你可以轻松地恢复检查以快速发现问题。
+不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。
#### DBC + 单元测试
-下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,类将回绕到开头。
+下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。
我们可以对这个队列做一些契约定义:
@@ -584,7 +584,7 @@ assert invariant();
- **postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果您对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果您的单元测试是足够的,那么你应当对不变性保持一定的信心。
+ **postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。
@@ -758,9 +758,9 @@ assert invariant();
#### 使用Guava前置条件
-在非严格DBC中, 我指出,前置条件是DbC中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为Java在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。
+在非严格DBC中,前置条件是DbC中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为Java在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。
-谷歌的Guava库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里您可以看到它们的简单用法。库的设计人员建议你静态导入前置条件:
+谷歌的Guava库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件:
```java
// validating/GuavaPreconditions.java
@@ -882,9 +882,9 @@ NullPointerException
*/
```
-虽然Guava的前置条件适用于所有类型,但我只演示 **字符串(String)** 类型。**test()**方法需要一个**Consumer**,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
+虽然Guava的前置条件适用于所有类型,但我只演示 **字符串(String)** 类型。**test()** 方法需要一个Consumer,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。
-每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以您可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null **值的对象构造:
+每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以你可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null** 值的对象构造:
/
From ec3265b4376a7d1562ee502d95394d3fbc7be239 Mon Sep 17 00:00:00 2001
From: zzh1991
Date: Wed, 28 Aug 2019 21:50:16 +0800
Subject: [PATCH 064/449] fix typos for introduction and preface
---
docs/book/00-Introduction.md | 5 ++---
docs/book/00-Preface.md | 11 +++--------
2 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md
index 598f17ab..083b5a1b 100644
--- a/docs/book/00-Introduction.md
+++ b/docs/book/00-Introduction.md
@@ -17,11 +17,11 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
编程的过程就是复杂性管理的过程:业务问题的复杂性,以及依赖的计算机的复杂性。由于这种复杂性,我们的大多数软件项目都失败了。
-许多语言设计决策时都考虑到了复杂性,并试图降低语言的复杂性,但在设计过程中遇到了一些更棘手的问题,最终导致语言设计不可避免地“碰壁”,复杂性增加。例如,C++ 必须向后兼容 C(允许 C 程序员轻松迁移),并且效率很高。这些目标非常实用,并且也是 C++ 在编程界取得了成功的原因之一,但同时也引入了额外的复杂性,导致某些用C++编写的项目开发失败。当然,你可以责怪程序员和管理人员手艺不精,但如果有一种编程语言可以帮助你在开发过程中发现错误,那岂不是更好?
+许多语言设计决策时都考虑到了复杂性,并试图降低语言的复杂性,但在设计过程中遇到了一些更棘手的问题,最终导致语言设计不可避免地“碰壁”,复杂性增加。例如,C++ 必须向后兼容 C(允许 C 程序员轻松迁移),并且效率很高。这些目标非常实用,并且也是 C++ 在编程界取得了成功的原因之一,但同时也引入了额外的复杂性,导致某些用 C++ 编写的项目开发失败。当然,你可以责怪程序员和管理人员手艺不精,但如果有一种编程语言可以帮助你在开发过程中发现错误,那岂不是更好?
虽然 VB(Visual BASIC)绑定在 BASIC 上,但 BASIC 实际上并不是一种可扩展的语言。大量扩展的堆积造成 VB 的语法难以维护。Perl 向后兼容 awk、sed、grep 以及其它要替换的 Unix 工具。因此它常常被诟病产生了一堆“只写代码”(*write-only code*,写代码的人自己都看不懂的代码)。另一方面,C ++,VB,Perl 和其他语言(如 SmallTalk)在设计时重点放在了对某些复杂问题的处理上,因而在解决这些特定类型的问题方面非常成功。
-通信革命使我们相互沟通更加便利。无论是一对一沟通,还是团队里的互相沟通,甚至是地球上不同地区的沟通。据说下一次革命需要的是一种全球性的思维,这种思维源于足量的人以及足量相互连接。不我不知道 Java 是否能成为这场革命的工具之一,但至少这种可能性让我觉得:我现在正在做的传道授业的事情是有意义的!
+通信革命使我们相互沟通更加便利。无论是一对一沟通,还是团队里的互相沟通,甚至是地球上不同地区的沟通。据说下一次革命需要的是一种全球性的思维,这种思维源于足量的人以及足量相互连接。我不知道 Java 是否能成为这场革命的工具之一,但至少这种可能性让我觉得:我现在正在做的传道授业的事情是有意义的!
## 前提条件
@@ -33,7 +33,6 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,
- 控制语句(例如 **if**),循环结构(例如 **while**)
-
可能你已在学校、书籍或网络上了学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。
你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面对对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。
diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md
index be8f27e4..93871fca 100644
--- a/docs/book/00-Preface.md
+++ b/docs/book/00-Preface.md
@@ -1,5 +1,4 @@
-
# 前言
> 本书基于 Java 8 版本来教授当前 Java 编程的最优实践。
@@ -16,18 +15,17 @@
本书的教学目标:
-
1. 循序渐进地呈现学习内容,以便于你在不依赖后置知识框架的情况下轻松完成现有的学习任务,同时尽量保证前面章节的内容在后面的学习中得到运用。如果确有必要引入我们还没学习到的知识概念,我会做个简短地介绍。
2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意看到我的读者们因此能保持饶有兴趣地学习。
-3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要取知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。
+3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。
4. 希望本书能为你打下坚实的基础,方便你将来学习更难的课程和书籍。
## 语言设计错误
-每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错了什么还要糟糕。哎,每一种设语言/库的设计错误都会永久地嵌入在 Java 的发行版中。
+每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错了什么还要糟糕。哎,每一种语言/库的设计错误都会永久地嵌入在 Java 的发行版中。
诺贝尔经济学奖得主约瑟夫·斯蒂格利茨(*Joseph Stiglitz*)有一套适用于这里的人生哲学,叫做“承诺升级理论”:继续犯错误的成本由别人承担,而承认错误的成本由自己承担。
@@ -39,7 +37,7 @@
对编程语言的设计错误理解至关重要,甚至影响程序员的开发效率。部分公司在开发过程中避免使用语言的某些功能特性。这些功能特性表面上看起来高大上,但是弄不好却可能出现意料之外的错误,影响整个开发进程。
-已知的语言设计错误会给新的一门编程语言的作者提供参考。探索一门语言能做什么是很有趣的一件事,而语言设计错误能提醒你哪些“坑”是不能再趟的。多年以来,我一直感觉 Java 的设计者们有点脱离群众。Java 的有些设计错误错的太明显,我甚至怀疑设计者们到底是为出于服务用户还是其他动机设计了这些功能。Java 语言有许多臭名昭著的设计错误,很可能这也是诱惑所在。Java 似乎并不尊重开发者。为此我很长时间内不想于 Java 有任何瓜葛。很大程度上,这也是我不想碰 Java 的原因吧。
+已知的语言设计错误会给新的一门编程语言的作者提供参考。探索一门语言能做什么是很有趣的一件事,而语言设计错误能提醒你哪些“坑”是不能再趟的。多年以来,我一直感觉 Java 的设计者们有点脱离群众。Java 的有些设计错误错的太明显,我甚至怀疑设计者们到底是为出于服务用户还是其他动机设计了这些功能。Java 语言有许多臭名昭著的设计错误,很可能这也是诱惑所在。Java 似乎并不尊重开发者。为此我很长时间内不想与 Java 有任何瓜葛。很大程度上,这也是我不想碰 Java 的原因吧。
如今再审视 Java 8,我发现了许多变化。设计者们对于语言和用户的态度似乎发生了根本性上的改变。忽视用户投诉多年之后,Java 的许多功能和类库都已被搞砸了。
@@ -93,9 +91,6 @@ Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你
感谢所有花时间和精力来科罗拉多州克雷斯特德比特(Crested Butte, Colorado)镇参加我的研讨会,开发商务聚会和其他活动的人!你们的贡献可能不容易看到,但却非常重要!
-
-
-
## 献礼
> 谨以此书献给我敬爱的父亲 E. Wayne Eckel。
From cbc72f709aba8b49227d20855e16d28990a7c384 Mon Sep 17 00:00:00 2001
From: RoyIronGrey <15665512229@163.com>
Date: Thu, 29 Aug 2019 11:26:31 +0800
Subject: [PATCH 065/449] Issue #223
---
docs/book/16-Validating-Your-Code.md | 409 +++++++++++++++++++++++++++
1 file changed, 409 insertions(+)
diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md
index 07b27494..3e3530ae 100644
--- a/docs/book/16-Validating-Your-Code.md
+++ b/docs/book/16-Validating-Your-Code.md
@@ -915,10 +915,419 @@ public class NonNullConstruction {
## 测试驱动开发
+之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码,而且还可以得到更好的代码设计。 一般情况下这个说法都是正确的。 一旦我想到“我将如何测试我的代码?”,这个想法将使我的代码产生变化,并且往往是从“可测试”转变为“可用”。
+
+纯粹的TDD主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 **String** 中字符的大小写。 让我们随意添加一些约束:**String** 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。
+
+此示例与标准TDD不同,因为它的作用在于接收 **StringInverter** 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 所以 **StringInverter** 将作为接口来满足这个要求:
+
+```java
+// validating/StringInverter.java
+package validating;
+
+interface StringInverter {
+ String invert(String str);
+}
+```
+
+现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了JUnit5中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。
+
+JUnit5 提供了几种动态生成测试的方法,但这里使用的方法可能是最复杂的。 **DynamicTest.stream() **方法采用了:
+
+- 对象集合上的迭代器 (versions) ,这个迭代器在不同组的测试中是不同的。 迭代器生成的对象可以是任何类型,但是只能有一种对象生成,因此对于存在多个不同的对象类型时,必须人为地将它们打包成单个类型。
+- **Function**,它从迭代器获取对象并生成描述测试的 **String** 。
+- **Consumer**,它从迭代器获取对象并包含基于该对象的测试代码。
+
+在此示例中,所有代码将在 **testVersions()** 中进行组合以防止代码重复。 迭代器生成的对象是对 **DynamicTest** 的不同实现,这些对象体现了对接口不同版本的实现:
+
+```java
+// validating/tests/DynamicStringInverterTests.java
+package validating;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.DynamicTest.*;
+
+class DynamicStringInverterTests {
+ // Combine operations to prevent code duplication:
+ Stream testVersions(String id,
+ Function test) {
+ List versions = Arrays.asList(
+ new Inverter1(), new Inverter2(),
+ new Inverter3(), new Inverter4());
+ return DynamicTest.stream(
+ versions.iterator(),
+ inverter -> inverter.getClass().getSimpleName(),
+ inverter -> {
+ System.out.println(
+ inverter.getClass().getSimpleName() +
+ ": " + id);
+ try {
+ if(test.apply(inverter) != "fail")
+ System.out.println("Success");
+ } catch(Exception | Error e) {
+ System.out.println(
+ "Exception: " + e.getMessage());
+ }
+ }
+ );
+ }
+ String isEqual(String lval, String rval) {
+ if(lval.equals(rval))
+ return "success";
+ System.out.println("FAIL: " + lval + " != " + rval);
+ return "fail";
+ }
+ @BeforeAll
+ static void startMsg() {
+ System.out.println(
+ ">>> Starting DynamicStringInverterTests <<<");
+ }
+ @AfterAll
+ static void endMsg() {
+ System.out.println(
+ ">>> Finished DynamicStringInverterTests <<<");
+ }
+ @TestFactory
+ Stream basicInversion1() {
+ String in = "Exit, Pursued by a Bear.";
+ String out = "eXIT, pURSUED BY A bEAR.";
+ return testVersions(
+ "Basic inversion (should succeed)",
+ inverter -> isEqual(inverter.invert(in), out)
+ );
+ }
+ @TestFactory
+ Stream basicInversion2() {
+ return testVersions(
+ "Basic inversion (should fail)",
+ inverter -> isEqual(inverter.invert("X"), "X"));
+ }
+ @TestFactory
+ Stream disallowedCharacters() {
+ String disallowed = ";-_()*&^%$#@!~`0123456789";
+ return testVersions(
+ "Disallowed characters",
+ inverter -> {
+ String result = disallowed.chars()
+ .mapToObj(c -> {
+ String cc = Character.toString((char)c);
+ try {
+ inverter.invert(cc);
+ return "";
+ } catch(RuntimeException e) {
+ return cc;
+ }
+ }).collect(Collectors.joining(""));
+ if(result.length() == 0)
+ return "success";
+ System.out.println("Bad characters: " + result);
+ return "fail";
+ }
+ );
+ }
+ @TestFactory
+ Stream allowedCharacters() {
+ String lowcase = "abcdefghijklmnopqrstuvwxyz ,.";
+ String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.";
+ return testVersions(
+ "Allowed characters (should succeed)",
+ inverter -> {
+ assertEquals(inverter.invert(lowcase), upcase);
+ assertEquals(inverter.invert(upcase), lowcase);
+ return "success";
+ }
+ );
+ }
+ @TestFactory
+ Stream lengthNoGreaterThan30() {
+ String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ assertTrue(str.length() > 30);
+ return testVersions(
+ "Length must be less than 31 (throws exception)",
+ inverter -> inverter.invert(str)
+ );
+ }
+ @TestFactory
+ Stream lengthLessThan31() {
+ String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ assertTrue(str.length() < 31);
+ return testVersions(
+ "Length must be less than 31 (should succeed)",
+ inverter -> inverter.invert(str)
+ );
+ }
+}
+```
+
+在一般的测试中,你可能认为在进行一个结果为失败的测试时应该停止代码构建。 但是在这里,我们只希望系统报告问题,但仍然继续运行,以便你可以看到不同版本的 **StringInverter** 的效果。
+
+每个使用 **@TestFactory** 注释的方法都会生成一个 **DynamicTest** 对象的 **Stream**(通过 **testVersions()** ),每个 JUnit 都像常规的 **@Test** 方法一样执行。
+
+现在测试都已经准备好了,我们就可以开始实现 **StringInverter **了。 我们从一个仅返回其参数的假的实现类开始:
+
+```java
+// validating/Inverter1.java
+package validating;
+public class Inverter1 implements StringInverter {
+ public String invert(String str) { return str; }
+}
+```
+
+接下来我们实现反转操作:
+
+```java
+// validating/Inverter2.java
+package validating;
+import static java.lang.Character.*;
+public class Inverter2 implements StringInverter {
+ public String invert(String str) {
+ String result = "";
+ for(int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ result += isUpperCase(c) ?
+ toLowerCase(c) :
+ toUpperCase(c);
+ }
+ return result;
+ }
+}
+```
+
+现在添加代码以确保输入不超过30个字符:
+
+```java
+// validating/Inverter3.java
+package validating;
+import static java.lang.Character.*;
+public class Inverter3 implements StringInverter {
+ public String invert(String str) {
+ if(str.length() > 30)
+ throw new RuntimeException("argument too long!");
+ String result = "";
+ for(int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ result += isUpperCase(c) ?
+ toLowerCase(c) :
+ toUpperCase(c);
+ }
+ return result;
+ }
+}
+```
+
+最后,我们排除了不允许的字符:
+
+```java
+// validating/Inverter4.java
+package validating;
+import static java.lang.Character.*;
+public class Inverter4 implements StringInverter {
+ static final String ALLOWED =
+ "abcdefghijklmnopqrstuvwxyz ,." +
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ public String invert(String str) {
+ if(str.length() > 30)
+ throw new RuntimeException("argument too long!");
+ String result = "";
+ for(int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if(ALLOWED.indexOf(c) == -1)
+ throw new RuntimeException(c + " Not allowed");
+ result += isUpperCase(c) ?
+ toLowerCase(c) :
+ toUpperCase(c);
+ }
+ return result;
+ }
+}
+```
+
+你将从测试输出中看到,每个版本的 **Inverter** 都几乎能通过所有测试。 当你在进行测试优先的开发时会有相同的体验。
+
+**DynamicStringInverterTests.java** 仅是为了显示TDD过程中不同 **StringInverter** 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 **StringInverter** 类直到它满足所有测试:
+
+```java
+// validating/tests/StringInverterTests.java
+package validating;
+import java.util.*;
+import java.util.stream.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class StringInverterTests {
+ StringInverter inverter = new Inverter4();
+ @BeforeAll
+ static void startMsg() {
+ System.out.println(">>> StringInverterTests <<<");
+ }
+ @Test
+ void basicInversion1() {
+ String in = "Exit, Pursued by a Bear.";
+ String out = "eXIT, pURSUED BY A bEAR.";
+ assertEquals(inverter.invert(in), out);
+ }
+ @Test
+ void basicInversion2() {
+ expectThrows(Error.class, () -> {
+ assertEquals(inverter.invert("X"), "X");
+ });
+ }
+ @Test
+ void disallowedCharacters() {
+ String disallowed = ";-_()*&^%$#@!~`0123456789";
+ String result = disallowed.chars()
+ .mapToObj(c -> {
+ String cc = Character.toString((char)c);
+ try {
+ inverter.invert(cc);
+ return "";
+ } catch(RuntimeException e) {
+ return cc;
+ }
+ }).collect(Collectors.joining(""));
+ assertEquals(result, disallowed);
+ }
+ @Test
+ void allowedCharacters() {
+ String lowcase = "abcdefghijklmnopqrstuvwxyz ,.";
+ String upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.";
+ assertEquals(inverter.invert(lowcase), upcase);
+ assertEquals(inverter.invert(upcase), lowcase);
+ }
+ @Test
+ void lengthNoGreaterThan30() {
+ String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ assertTrue(str.length() > 30);
+ expectThrows(RuntimeException.class, () -> {
+ inverter.invert(str);
+ });
+ }
+ @Test
+ void lengthLessThan31() {
+ String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+ assertTrue(str.length() < 31);
+ inverter.invert(str);
+ }
+}
+```
+
+你可以通过这种方式进行开发:一开始在测试中建立你期望程序应有的所有特性,然后你就能在实现中一步步添加功能,直到所有测试通过。 完成后,你还可以在将来通过这些测试来得知(或让其他任何人得知)当修复错误或添加功能时,代码是否被破坏了。 TDD的目标是产生更好,更周全的测试,因为在完全实现之后尝试实现完整的测试覆盖通常会产生匆忙或无意义的测试。
+
+### 测试驱动 vs. 测试优先
+
+虽然我自己还没有达到测试优先的意识水平,但我最感兴趣的是来自测试优先中的“测试失败的书签”这一概念。 当你离开你的工作一段时间后,重新回到工作进展中,甚至找到你离开时工作到的地方有时会很有挑战性。 然而,以失败的测试为书签能让你找到之前停止的地方。 这似乎让你能更轻松地暂时离开你的工作,因为不用担心找不到工作进展的位置。
+
+纯粹的测试优先编程的主要问题是它假设你事先了解了你正在解决的问题。 根据我自己的经验,我通常是从实验开始,而只有当我处理问题一段时间后,我对它的理解才会达到能给它编写测试的程度。 当然,偶尔会有一些问题在你开始之前就已经完全定义,但我个人并不常遇到这些问题。 实际上,可能用“*面向测试的开发* ( *Test-Oriented Development* )”这个短语来描述编写测试良好的代码或许更好。
+
## 日志
+### 日志会给出正在运行的程序的各种信息。
+
+在调试程序中,日志可以是显示程序运行过程中的普通状态数据(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。
+
+在调试期间,日志也能带来好处。 如果没有日志,你可能会尝试通过插入 **println()** 语句来打印出程序的行为。 本书中的一些例子使用了这种技术,并且在没有调试器的情况下(下文中很快就会介绍这样一个主题),它就是你唯一的工具。 但是,一旦你确定程序正常运行,你可能会将 **println()** 语句注释或者删除。 然而,如果你遇到更多错误,你可能又需要运行它们。因此,如果能够只在需要时轻松启用输出程序状态就好多了。
+
+程序员在日志包可供使用之前,都只能依赖 Java 编译器移除未调用的代码。 如果 **debug** 是一个 **static final boolean **,你就可以这么写:
+
+```java
+if(debug) {
+ System.out.println("Debug info");
+}
+```
+
+然后,当 **debug **为 **false **时,编译器将移除大括号内的代码。 因此,未调用的代码不会对运行时产生影响。 使用这种方法,你可以在整个程序中放置跟踪代码,并轻松启用和关闭它。 但是,该技术的一个缺点是你必须重新编译代码才能启用和关闭跟踪语句。因此,通过更改配置文件来修改日志属性,从而起到启用跟踪语句但不用重新编译程序会更方便。
+
+业内普遍认为标准 Java 发行版本中的日志包 **(java.util.logging)** 的设计相当糟糕。 大多数人会选择其他的替代日志包。如 *Simple Logging Facade for Java(SLF4J)* ,它为多个日志框架提供了一个封装好的调用方式,这些日志框架包括 **java.util.logging** , **logback** 和 **log4j **。 SLF4J 允许用户在部署时插入所需的日志框架。
+
+SLF4J 提供了一个复杂的工具来报告程序的信息,它的效率与前面示例中的技术几乎相同。 对于非常简单的信息日志记录,你可以执行以下操作:
+
+```java
+// validating/SLF4JLogging.java
+import org.slf4j.*;
+public class SLF4JLogging {
+ private static Logger log =
+ LoggerFactory.getLogger(SLF4JLogging.class);
+ public static void main(String[] args) {
+ log.info("hello logging");
+ }
+}
+/* Output:
+2017-05-09T06:07:53.418
+[main] INFO SLF4JLogging - hello logging
+*/
+```
+
+日志输出中的格式和信息,甚至输出是否正常或“错误”都取决于 SLF4J 所连接的后端程序包是怎样实现的。 在上面的示例中,它连接到的是 **logback** 库(通过本书的 **build.gradle** 文件),并显示为标准输出。
+
+如果我们修改 **build.gradle **从而不使用内置在 JDK 中的日志包作为后端,则输出显示为错误输出,如下所示:
+
+**Aug 16, 2016 5:40:31 PM InfoLogging main**
+**INFO: hello logging**
+
+日志系统会检测日志消息处所在的的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。
+
+### 日志等级
+
+SLF4J 提供了多个等级的日志消息。下面这个例子以“严重性”的递增顺序对它们作出演示:
+
+```java
+// validating/SLF4JLevels.java
+import org.slf4j.*;
+public class SLF4JLevels {
+ private static Logger log =
+ LoggerFactory.getLogger(SLF4JLevels.class);
+ public static void main(String[] args) {
+ log.trace("Hello");
+ log.debug("Logging");
+ log.info("Using");
+ log.warn("the SLF4J");
+ log.error("Facade");
+ }
+}
+/* Output:
+2017-05-09T06:07:52.846
+[main] TRACE SLF4JLevels - Hello
+2017-05-09T06:07:52.849
+[main] DEBUG SLF4JLevels - Logging
+2017-05-09T06:07:52.849
+[main] INFO SLF4JLevels - Using
+2017-05-09T06:07:52.850
+[main] WARN SLF4JLevels - the SLF4J
+2017-05-09T06:07:52.851
+[main] ERROR SLF4JLevels - Facade
+*/
+```
+
+你可以按等级来查找消息。 级别通常设置在单独的配置文件中,因此你可以重新配置而无需重新编译。 配置文件格式取决于你使用的后端日志包实现。 如 **logback** 使用 XML :
+
+```xml
+
+
+
+
+
+
+%d{yyyy-MM-dd'T'HH:mm:ss.SSS}
+[%thread] %-5level %logger - %msg%n
+
+
+
+
+
+
+
+```
+
+你可以尝试将 ** **行更改为其他级别,然后重新运行该程序查看日志输出的更改情况。 如果你没有写 **logback.xml** 文件,日志系统将采取默认配置。
+
+这只是 SLF4J 最简单的介绍和一般的日志消息,但也足以作为使用日志的基础 - 你可以沿着这个进行更长久的学习和实践。你可以查阅 [SLF4J 文档](http://www.slf4j.org/manual.html)来获得更深入的信息。
+
## 调试
From 5be4e979ad4c7eb970c0ac7bc8d607d3dfb8cf27 Mon Sep 17 00:00:00 2001
From: JerryQiang
Date: Thu, 29 Aug 2019 15:17:45 +0800
Subject: [PATCH 066/449] update stack
---
docs/book/01-What-is-an-Object.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md
index 32db8473..83afe39b 100644
--- a/docs/book/01-What-is-an-Object.md
+++ b/docs/book/01-What-is-an-Object.md
@@ -232,7 +232,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共
还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。
-在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:
+在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:
1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。
@@ -260,7 +260,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共
现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。
-对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在堆栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。
+对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。
然而相对的,我们也牺牲了程序的灵活性。因为在编写代码时,我们必须要弄清楚对象的数量、生存时间还有类型。如果我们要用它来解决一个相当普遍的问题时(如计算机辅助设计、仓库管理或空中交通管制等),限制就太大了。
From fd76eba770f3a0f378b3051208ff071999aca934 Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Thu, 29 Aug 2019 19:44:18 +0800
Subject: [PATCH 067/449] =?UTF-8?q?revision[14]=20Optional=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 7d4340fc..37fa8f33 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -732,6 +732,7 @@ Not much of a cheese shop really is it
幸运的是,我们稍后就会知道如何解决这个问题。
+
## 中间操作
中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。
@@ -983,9 +984,9 @@ class FunctionMap3 {
### 在 `map()` 中组合流
-假设我们现在有了一个传入的元素流,并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要的只是产生流的元素,生成的却是一个元素流的流。
+假设我们现在有了一个传入的元素流,并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。
-`flatMap()` 在这里做了两件事情:获取流产生( stream-producing)函数,并将其应用于新到的元素(如 `map()` 所做的),然后获取每一个流并将其“扁平”为元素。所以它的输出只是元素。
+`flatMap()` 做了两件事:将产生流的函数应用在每个元素上(与 `map()` 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。
`flatMap(Function)`:当 `Function` 产生流时使用。
@@ -995,7 +996,7 @@ class FunctionMap3 {
`flatMapToDouble(Function)`:当 `Function` 产生 `DoubleStream` 时使用。
-为了弄清它的工作原理,我们从 `map()` 的一个刻意设计的函数开。该函数接受一个整数并产生一个字符串流:
+为了弄清它的工作原理,我们从传入一个刻意设计的函数给 `map()` 开始。该函数接受一个整数并产生一个字符串流:
```java
// streams/StreamOfStreams.java
@@ -1018,7 +1019,7 @@ java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
```
-我们天真的希望能够得到字符串流,但是结果却是流元素为“Head”流的流。我们可以使用 `flatMap()` 解决这个问题:
+我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用 `flatMap()` 解决这个问题:
```java
// streams/FlatMap.java
@@ -1046,9 +1047,9 @@ Fozzie
Beaker
```
-从映射返回的每个流都会自动扁平为其组件字符串。
+从映射返回的每个流都会自动扁平为组成它的字符串。
-如下是另一演示,我们从一个整数流开始,使用每一个整数去创建更多的随机数。
+下面是另一个演示,我们从一个整数流开始,然后使用每一个整数去创建更多的随机数。
```java
// streams/StreamOfRandoms.java
@@ -1071,7 +1072,7 @@ public class StreamOfRandoms {
58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1
```
-在这里我们引入了 `concat()`,它以参数顺序组合了两个流。 如此,我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。
+在这里我们引入了 `concat()`,它以参数顺序组合两个流。 如此,我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。
因为 `rand.ints()` 产生的是一个 `IntStream`,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数形式。
@@ -1098,7 +1099,7 @@ public class FileToWords {
**注意**:`\\W+` 是一个正则表达式。他表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\w` 表示“单词字符”。
-我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:
+我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:
```java
.flatMap(line -> Arrays.stream(line.split("\\W+"))))
@@ -1131,8 +1132,6 @@ Not much of a cheese shop really
在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。
-
-
## Optional类
From 01fb64b1c5fc2e67fb206acb5182ab4fae5894ca Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Fri, 30 Aug 2019 05:32:30 +0800
Subject: [PATCH 068/449] autocommit
---
docs/book/04-Operators.md | 2 +-
docs/book/05-Control-Flow.md | 2 +-
docs/book/14-Streams.md | 7 +++----
docs/sidebar.md | 2 +-
4 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index 307cd258..2750e053 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -10,7 +10,7 @@ Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于
如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。
-## 使用Java运算符
+## 开始使用
运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。
diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md
index dabc664e..2d630af9 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 和 false
+## true和false
所有的条件语句都利用条件表达式的“真”或“假”来决定执行路径。举例:
`a == b`。它利用了条件表达式 `==` 来比较 `a` 与 `b` 的值是否相等。 该表达式返回 `true` 或 `false`。代码示例:
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 37fa8f33..9697a85f 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -1677,20 +1677,19 @@ Signal(dash)
在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的行为。
-
+
## 终端操作
-
-这些操作获取一个流并产生一个最终结果;它们不会像后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。
+这些操作获取一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。
### 转化成数组(Convert to an Array)
- `toArray()`:将流转换成适当类型的数组。
- `toArray(generator)`:在特殊情况下,生成器用于分配你自己的数组存储。
-如果流操作产生的结果必须是数组形式,这是非常有用的。例如,假设我们想要以一种我们可以将它们作为流重用的方式捕获随机数,这样我们每次都可以得到相同的流。 我们可以通过将它们存储在一个数组中来实现:
+这组方法在流操作产生的结果必须是数组形式很有用。假如我们想在流里复用获取的随机数,以便于每次都可以得到相同的流们,可以将他们保存到数组中。代码示例:
```java
// streams/RandInts.java
diff --git a/docs/sidebar.md b/docs/sidebar.md
index e99b70ea..d1d99dcd 100644
--- a/docs/sidebar.md
+++ b/docs/sidebar.md
@@ -33,7 +33,7 @@
* [编码风格](book/03-Objects-Everywhere.md#编码风格)
* [本章小结](book/03-Objects-Everywhere.md#本章小结)
## [第四章 运算符](book/04-Operators.md)
- * [使用说明](book/04-Operators.md#使用说明)
+ * [开始使用](book/04-Operators.md#开始使用)
* [优先级](book/04-Operators.md#优先级)
* [赋值](book/04-Operators.md#赋值)
* [算术运算符](book/04-Operators.md#算术运算符)
From 4ab27d4a621b530faee0b99765a6d060f3bee8bc Mon Sep 17 00:00:00 2001
From: LingCoder
Date: Fri, 30 Aug 2019 06:48:15 +0800
Subject: [PATCH 069/449] =?UTF-8?q?[ISSUE=20#40]=E6=A0=A1=E8=AE=A2?=
=?UTF-8?q?=E6=B5=81=E5=BC=8F=E7=BC=96=E7=A8=8B-=E7=BB=88=E7=AB=AF?=
=?UTF-8?q?=E6=93=8D=E4=BD=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 56 +++++++++++++++++++++++------------------
1 file changed, 32 insertions(+), 24 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 9697a85f..6e5e3232 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -1684,12 +1684,12 @@ Signal(dash)
这些操作获取一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。
-### 转化成数组(Convert to an Array)
-
+
+### 转化数组
- `toArray()`:将流转换成适当类型的数组。
-- `toArray(generator)`:在特殊情况下,生成器用于分配你自己的数组存储。
+- `toArray(generator)`:在特殊情况下,生成器用于分配自定义的数组存储。
-这组方法在流操作产生的结果必须是数组形式很有用。假如我们想在流里复用获取的随机数,以便于每次都可以得到相同的流们,可以将他们保存到数组中。代码示例:
+这组方法在流操作产生的结果必须是数组形式时很有用。假如我们想在流里复用获取的随机数,可以将他们保存到数组中。代码示例:
```java
// streams/RandInts.java
@@ -1704,18 +1704,19 @@ public class RandInts {
}
```
-一个包含 100 个数值范围在 0 到 10000 之间的随机数流转换成为数组并将其存储在 **rints** 中,所以在每次调用 `rands()` 的时候你都可以重复地获取相同的流。
+上例将100个数值范围在0-10000之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。
-### 对每个元素应用最终操作(Apply a Final Operation to Every Element)
+
+### 应用最终操作
-- `forEach(Consumer)`:你已经看到很多次 `System.out::println` 作为 **Consumer** 函数。
-- `forEachOrdered(Consumer)`: 这个形式保证了 forEach 的操作顺序是原始流顺序。
+- `forEach(Consumer)`:常见的,如 `System.out::println` 作为 **Consumer** 函数。
+- `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。
-第一种形式是显式地设计为以任何顺序操作元素,这只在引入 `parallel()` 操作时才有意义。在 [并发]() 章节之前我们不会深入研究这个问题,但是这里有一个简单的介绍:`parallel()` 告诉 Java 尝试在多个处理器上运行操作。它可以做到这一点是因为我们使用流——流可以被分割为多个流(通常一个流一个处理器)并且每个流运行在不同的处理器上。因为我们使用的是内部循环而不是外部循环,这是可行的。
+第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问。这里简单介绍下`parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。这对内部循环是可行的。
-在你对 `parallel()` 的看似容易感到过于兴奋之前,它实际上相当棘手,所以请等到我们进入 [并发]() 编程章节。
+`parallel()` 看似简单,实则棘手。更多内容,在稍后学习的[并发编程](24-Concurrent-Programming.md) 编程章节。
-但是,我们可以通过将 `parallel()` 引入一个示例来了解 `forEachOrdered(Consumer)` 的效果和需求:
+下例引入了 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:
```java
// streams/ForEach.java
@@ -1747,16 +1748,19 @@ public class ForEach {
258 555 693 861 961 429 868 200 522 207 288 128 551 589
```
-我已经隔离了 `SZ`,以便轻松尝试不同的尺寸。 然而,即使是 14 的 `SZ` 也会产生有趣的结果。在第一个流中,我们没有使用 `parallel()` 所以按照它们从 `rands()` 出现的顺序显示结果。第二个流我们引入了 `parallel()` ,即使是很小的流,你也会看到输出结果和之前的顺序并不一样。这是由于多个处理器正在解决这个问题,如果你多次运行这个程序,你将会看到这个结果均是不同的,这是由于多个处理器同时处理该问题而产生的非确定性因素。
+为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果熟悉均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。
+
+在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。
-最后一个流依旧使用了 `parallel()`,但是通过使用 `forEachOrdered()` 强制结果保留原始顺序。因此使用 `parallel()` 对于非并行流没有任何影响。
+
+### 收集
-### 收集(Collecting)
+- `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。
+- `collect(Supplier, BiConsumer, BiConsumer)`:同上,参数1 **Supplier** 创建了一个新结果集合,参数2 **BiConsumer** 将下一个元素包含到结果中,参数3 **BiConsumer** 用于将两个值组合起来。
-- `collect(Collector)`:使用 **Collector** 来累计流元素到结果集合中。
-- `collect(Supplier, BiConsumer, BiConsumer)`:同上,但是 **Supplier** 创建了一个新的结果集合,第一个 **BiConsumer** 是将下一个元素包含在结果中的函数,而第二个 **BiConsumer** 是用于将两个值组合起来。
+在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现。可通过查看 `java.util.stream.Collectors` 部分的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。
-你只看到了少数几个 **Collectors** 对象的示例。如果你查看 ` java.util.stream.Collectors`的文档,你会发现其中的一些实现非常复杂。例如,我们可以将元素收集到任意一种特定的集合中。假设我们想将我们的元素最终在 **TreeSet** 中,以保证它们总是有序的。在 **Collectors** 里面没有特定的 `toTreeSet()` 方法,但是你可以使用 ` Collectors.toCollection()`并为任何类型的Collection提供构造函数引用。 该程序将文件中的单词拉入**TreeSet** :
+假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过 `Collectors.toCollection()` 来为任何类型的集合提供构造函数引用。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例:
```java
// streams/TreeSetOfWords.java
@@ -1791,9 +1795,9 @@ stream, streams, throws, toCollection, trim, util,
void, words2]
```
-`Files.lines()` 打开 **Path** 并将其转换成为行流。下一行代码将根据一个或者多个非单词字符(\\\\w+)作为边界对行进行分割,然后使用 `Arrays.stream()` 将其转化成为流,并将结果扁平映射成为单词流。`matches(\\d+)` 寻找并移除那些全是数字的字符串(注意 **words2** 是通过的)。接下来我们使用 `String.trim()` 去除单词两边的空白,`filter()`过滤所有长度小于 3 的单词,然后只获取 100 个单词,并最终将其塞入到 **TreeSet** 中。
+**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。
-我们也可以在流中生成 **Map**:
+我们也可以在流中生成 **Map**。代码示例:
```java
// streams/MapCollector.java
@@ -1875,7 +1879,8 @@ cheese
在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。
-### 组合所有流元素(Combining All Stream Elements)
+
+### 组合所有流元素
- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。
- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。
@@ -1934,7 +1939,8 @@ Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结
`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其 size 小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。因此你会取得第一个 size 小于 50 的 `Frobnitz`,只要找到了就这个结果就会紧紧地攥住它,即使有其他候选者出现。虽然这是一个非常奇怪的约束,但是它确实让你对 `reduce()` 有了更多的了解。
-### 匹配(Matching)
+
+### 匹配
- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。
- `anyMatch(Predicate)`:如果流中的一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
@@ -1986,7 +1992,7 @@ public class Matching {
`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于像我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。
-### 选择元素
+### 元素查找
- `findFirst()`:返回一个含有第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**。
- `findAny(`:返回含有任意流元素的 **Optional**,如果流为空返回 **Optional.empty**。
@@ -2049,7 +2055,8 @@ three
`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字可选类型( numeric optional type),否则你使用的 **Optional** 类型为 `Optional`。
-### 信息(Informational)
+
+### 信息
- `count()`:流中的元素个数。
- `max(Comparator)`:根据所传入的 **Comparator** 所决定的“最大”元素。
@@ -2088,7 +2095,8 @@ you
`min()` 和 `max()` 的返回类型为 **Optional**,这需要我们使用 `orElse()`来解包。
-### 数字流信息(Information for Numeric Streams)
+
+### 数字流信息
- `average()` :求取流元素平均值。
- `max()` 和 `min()`:因为这些操作在数字流上面,所以不需要 **Comparator**。
From 64a30ccf64701349154395519bc4089a86f13645 Mon Sep 17 00:00:00 2001
From: My012 <15661239895@163.com>
Date: Fri, 30 Aug 2019 10:04:02 +0800
Subject: [PATCH 070/449] Update 09-Polymorphism.md
---
docs/book/09-Polymorphism.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md
index 4f569295..a6093b4a 100644
--- a/docs/book/09-Polymorphism.md
+++ b/docs/book/09-Polymorphism.md
@@ -819,7 +819,7 @@ class Amphibian extends Animal {
}
public class Frog extends Amphibian {
- private Characteristic = = new Characteristic("Croaks");
+ private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");
public Frog() {
From 54caeb7573bc2507d141bf506d0896754f99be95 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Fri, 30 Aug 2019 22:01:25 +0800
Subject: [PATCH 071/449] Update 10-Interfaces.md
#229
---
docs/book/10-Interfaces.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md
index f2a4f21b..927861c5 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -422,7 +422,7 @@ secondMethod
newMethod
```
-尽管 **Implementation2** 中定义 `newMethod()`,但是可以使用 `newMethod()` 了。
+尽管 **Implementation2** 中未定义 `newMethod()`,但是可以使用 `newMethod()` 了。
增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为*守卫方法*或*虚拟扩展方法*。
From a3a0d445deab52a10890624db83dbfde8e7bb770 Mon Sep 17 00:00:00 2001
From: cogitates
Date: Sat, 31 Aug 2019 12:44:47 +0800
Subject: [PATCH 072/449] =?UTF-8?q?Fix=20issue=20117,=20=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E6=95=B0=E7=BB=84=E9=83=A8=E5=88=86=20=E4=BA=94=EF=BC=8C?=
=?UTF-8?q?=E5=85=AD=EF=BC=8C=E4=B8=83=E4=B8=89=E4=B8=AA=E5=B0=8F=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: cogitates
---
docs/book/21-Arrays.md | 236 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 236 insertions(+)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index caeb1164..7e874a02 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -574,14 +574,250 @@ Lazy, Brown, Dog, &, friend]]
## 泛型数组
+一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组:
+
+```Java
+Peel[] peels = new Peel[10]; // Illegal
+```
+
+类型擦除需要删除参数类型信息,而且数组必须知道它们所保存的确切类型,以强制保证类型安全。
+
+但是,可以参数化数组本身的类型:
+
+```Java
+// arrays/ParameterizedArrayType.java
+
+class ClassParameter {
+ public T[] f(T[] arg) { return arg; }
+}
+
+class MethodParameter {
+ public static T[] f(T[] arg) { return arg; }
+}
+
+public class ParameterizedArrayType {
+ public static void main(String[] args) {
+ Integer[] ints = { 1, 2, 3, 4, 5 };
+ Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 };
+ Integer[] ints2 =
+ new ClassParameter().f(ints);
+ Double[] doubles2 =
+ new ClassParameter().f(doubles);
+ ints2 = MethodParameter.f(ints);
+ doubles2 = MethodParameter.f(doubles);
+ }
+}
+```
+
+比起使用参数化类,使用参数化方法很方便。您不必为应用它的每个不同类型都实例化一个带有参数的类,但是可以使它成为 **静态** 的。你不能总是选择使用参数化方法而不用参数化的类,但通常参数化方法是更好的选择。
+
+你不能创建泛型类型的数组,这种说法并不完全正确。是的,编译器不会让你 *实例化* 一个泛型的数组。但是,它将允许您创建对此类数组的引用。例如:
+
+```Java
+List[] ls;
+```
+
+无可争议的,这可以通过编译。尽管不能创建包含泛型的实际数组对象,但是你可以创建一个非泛型的数组并对其进行强制类型转换:
+
+```Java
+// arrays/ArrayOfGenerics.java
+import java.util.*;
+
+public class ArrayOfGenerics {
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) {
+ List[] ls;
+ List[] la = new List[10];
+ ls = (List[])la; // Unchecked cast
+ ls[0] = new ArrayList<>();
+
+ //- ls[1] = new ArrayList();
+ // error: incompatible types: ArrayList
+ // cannot be converted to List
+ // ls[1] = new ArrayList();
+ // ^
+
+ // The problem: List is a subtype of Object
+ Object[] objects = ls; // So assignment is OK
+ // Compiles and runs without complaint:
+ objects[1] = new ArrayList<>();
+
+ // However, if your needs are straightforward it is
+ // possible to create an array of generics, albeit
+ // with an "unchecked cast" warning:
+ List[] spheres =
+ (List[])new List[10];
+ Arrays.setAll(spheres, n -> new ArrayList<>());
+ }
+}
+```
+
+一旦你有了对 **List[]** 的引用 , 你会发现多了一些编译时检查。问题是数组是协变的,所以 **List[]** 也是一个 **Object[]** ,你可以用这来将 **ArrayList ** 分配进你的数组,在编译或者运行时都不会出错。
+
+如果你知道你不会进行向上类型转换,你的需求相对简单,那么可以创建一个泛型数组,它将提供基本的编译时类型检查。然而,一个泛型 **Collection** 实际上是一个比泛型数组更好的选择。
+
+一般来说,您会发现泛型在类或方法的边界上是有效的。在内部,擦除常常会使泛型不可使用。所以,就像下面的例子,不能创建泛型类型的数组:
+
+```Java
+// arrays/ArrayOfGenericType.java
+
+public class ArrayOfGenericType {
+ T[] array; // OK
+ @SuppressWarnings("unchecked")
+ public ArrayOfGenericType(int size) {
+ // error: generic array creation:
+ //- array = new T[size];
+ array = (T[])new Object[size]; // unchecked cast
+ }
+ // error: generic array creation:
+ //- public U[] makeArray() { return new U[10]; }
+}
+
+```
+
+擦除再次从中作梗,这个例子试图创建已经擦除的类型数组,因此它们是未知的类型。你可以创建一个 **对象** 数组,然后对其进行强制类型转换,但如果没有 **@SuppressWarnings** 注释,你将会得到一个 "unchecked" 警告,因为数组实际上不真正支持而且将对类型 **T** 动态检查 。这就是说,如果我创建了一个 **String[]** , Java将在编译时和运行时强制执行,我只能在数组中放置字符串对象。然而,如果我创建一个 **Object[]** ,我可以把除了基元类型外的任何东西放入数组。
+
+
## Arrays的fill方法
+通常情况下,当对数组和程序进行实验时,能够很轻易地生成充满测试数据的数组是很有帮助的。 Java 标准库 **Arrays** 类包括一个普通的 **fill()** 方法,该方法将单个值复制到整个数组,或者在对象数组的情况下,将相同的引用复制到整个数组:
+
+```Java
+// arrays/FillingArrays.java
+// Using Arrays.fill()
+import java.util.*;
+import static onjava.ArrayShow.*;
+
+public class FillingArrays {
+ public static void main(String[] args) {
+ int size = 6;
+ boolean[] a1 = new boolean[size];
+ byte[] a2 = new byte[size];
+ char[] a3 = new char[size];
+ short[] a4 = new short[size];
+ int[] a5 = new int[size];
+ long[] a6 = new long[size];
+ float[] a7 = new float[size];
+ double[] a8 = new double[size];
+ String[] a9 = new String[size];
+ Arrays.fill(a1, true);
+ show("a1", a1);
+ Arrays.fill(a2, (byte)11);
+ show("a2", a2);
+ Arrays.fill(a3, 'x');
+ show("a3", a3);
+ Arrays.fill(a4, (short)17);
+ show("a4", a4);
+ Arrays.fill(a5, 19);
+ show("a5", a5);
+ Arrays.fill(a6, 23);
+ show("a6", a6);
+ Arrays.fill(a7, 29);
+ show("a7", a7);
+ Arrays.fill(a8, 47);
+ show("a8", a8);
+ Arrays.fill(a9, "Hello");
+ show("a9", a9);
+ // Manipulating ranges:
+ Arrays.fill(a9, 3, 5, "World");
+ show("a9", a9);
+ }
+}gedan
+/* Output:
+a1: [true, true, true, true, true, true]
+a2: [11, 11, 11, 11, 11, 11]
+a3: [x, x, x, x, x, x]
+a4: [17, 17, 17, 17, 17, 17]
+a5: [19, 19, 19, 19, 19, 19]
+a6: [23, 23, 23, 23, 23, 23]
+a7: [29.0, 29.0, 29.0, 29.0, 29.0, 29.0]
+a8: [47.0, 47.0, 47.0, 47.0, 47.0, 47.0]
+a9: [Hello, Hello, Hello, Hello, Hello, Hello]
+a9: [Hello, Hello, Hello, World, World, Hello]
+*/
+
+```
+
+你既可以填充整个数组,也可以像最后两个语句所示,填充一系列的元素。但是由于你只能使用单个值调用 **Arrays.fill()** ,因此结果并非特别有用。
+
## Arrays的setAll方法
+在Java 8中, 在**RaggedArray.java** 中引入并在 **ArrayOfGenerics.java.Array.setAll()** 中重用。它使用一个生成器并生成不同的值,可以选择基于数组的索引元素(通过访问当前索引,生成器可以读取数组值并对其进行修改)。 **static Arrays.setAll()** 的重载签名为:
+
+* **void setAll(int[] a, IntUnaryOperator gen)**
+* **void setAll(long[] a, IntToLongFunction gen)**
+* **void setAll(double[] a, IntToDoubleFunctiongen)**
+* ** void setAll(T[] a, IntFunction extendsT> gen)**
+
+除了 **int** , **long** , **double** 有特殊的版本,其他的一切都由泛型版本处理。生成器不是 **Supplier** 因为它们不带参数,并且必须将 **int** 数组索引作为参数。
+
+```java
+// arrays/SimpleSetAll.java
+
+import java.util.*;
+import static onjava.ArrayShow.*;
+
+class Bob {
+ final int id;
+ Bob(int n) { id = n; }
+ @Override
+ public String toString() { return "Bob" + id; }
+}
+
+public class SimpleSetAll {
+ public static final int SZ = 8;
+ static int val = 1;
+ static char[] chars = "abcdefghijklmnopqrstuvwxyz"
+ .toCharArray();
+ static char getChar(int n) { return chars[n]; }
+ public static void main(String[] args) {
+ int[] ia = new int[SZ];
+ long[] la = new long[SZ];
+ double[] da = new double[SZ];
+ Arrays.setAll(ia, n -> n); // [1]
+ Arrays.setAll(la, n -> n);
+ Arrays.setAll(da, n -> n);
+ show(ia);
+ show(la);
+ show(da);
+ Arrays.setAll(ia, n -> val++); // [2]
+ Arrays.setAll(la, n -> val++);
+ Arrays.setAll(da, n -> val++);
+ show(ia);
+ show(la);
+ show(da);
+
+ Bob[] ba = new Bob[SZ];
+ Arrays.setAll(ba, Bob::new); // [3]
+ show(ba);
+
+ Character[] ca = new Character[SZ];
+ Arrays.setAll(ca, SimpleSetAll::getChar); // [4]
+ show(ca);
+ }
+}
+/* Output:
+[0, 1, 2, 3, 4, 5, 6, 7]
+[0, 1, 2, 3, 4, 5, 6, 7]
+[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
+[1, 2, 3, 4, 5, 6, 7, 8]
+[9, 10, 11, 12, 13, 14, 15, 16]
+[17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0]
+[Bob0, Bob1, Bob2, Bob3, Bob4, Bob5, Bob6, Bob7]
+[a, b, c, d, e, f, g, h]
+*/
+
+```
+
+* **[1]** 这里,我们只是将数组索引作为值插入数组。这将自动转化为 **long** 和 **double** 版本。
+* **[2]** 这个函数只需要接受索引就能产生正确结果。这个,我们忽略索引值并且使用 **val** 生成结果。
+* **[3]** 方法引用有效,因为 **Bob** 的构造器接收一个 **int** 参数。只要我们传递的函数接收一个 **int** 参数且能产生正确的结果,就认为它完成了工作。
+* **[4]** 为了处理除了 **int** ,**long** ,**double** 之外的基元类型,请为基元创建包装类的数组。然后使用 **setAll()** 的泛型版本。请注意,**getChar()** 生成基元类型,因此这是自动装箱到 **Character** 。
+
## 增量生成
From ce0eba262e5a65439fd02c0736dda6e3767d5357 Mon Sep 17 00:00:00 2001
From: sjsdfg <736777445@qq.com>
Date: Sun, 1 Sep 2019 15:13:16 +0800
Subject: [PATCH 073/449] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=99=84=E5=BD=95?=
=?UTF-8?q?=E5=AF=B9=E8=B1=A1=E5=BA=8F=E5=88=97=E5=8C=96=E7=BF=BB=E8=AF=91?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/Appendix-Object-Serialization.md | 959 +++++++++++++++++++++
1 file changed, 959 insertions(+)
diff --git a/docs/book/Appendix-Object-Serialization.md b/docs/book/Appendix-Object-Serialization.md
index 646dd1b2..3640de30 100644
--- a/docs/book/Appendix-Object-Serialization.md
+++ b/docs/book/Appendix-Object-Serialization.md
@@ -3,18 +3,977 @@
# 附录:对象序列化
+当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这么做肯定是有意义的,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用。这样,在下次运行程序时,该对象将被重建并且拥有的信息与在程序上次运行时它所拥有的信息相同。当然,你可以通过将信息写入文件或数据库来达到相同的效果,但是在使万物都成为对象的精神中,如果能够将一个对象声明为是“持久性”的,并为我们处理掉所有细节,那将会显得十分方便。
+
+Java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行 Unix 系统的计算机,然后在那里准确地重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心宇节的顺序或者其他任何细节。
+
+就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写人磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种"persistent"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。
+
+对象序列化的概念加入到语言中是为了支持两种主要特性。一是 Java 的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
+
+再者,对 Java Beans 来说,对象的序列化也是必需的(在撰写本文时被视为失败的技术),使用一个 Bean 时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复,这种具体工作就是由对象序列化完成的。
+
+只要对象实现了 Serializable 接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准库类都发生了改变,以便具备序列化特性-其中包括所有基本数据类型的封装器、所有容器类以及许多其他的东西。甚至 Class 对象也可以被序列化。
+
+要序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装在一个 ObjectOutputStream 对象内。这时,只需调用 writeObject() 即可将对象序列化,并将其发送给 OutputStream(对象化序列是基于字节的,因要使用 InputStream 和 OutputStream 继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个 InputStream 封装在 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的 Object,所以必须向下转型才能直接设置它们。
+
+对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪,依此类推。这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组以及成员对象。如果必须保持一套自己的对象序列化机制,那么维护那些可追踪到所有链接的代码可能会显得非常麻烦。然而,由于 Java 的对象序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子通过对链接的对象生成一个 worm(蠕虫)对序列化机制进行了测试。每个对象都与 worm 中的下一段链接,同时又与属于不同类(Data)的对象引用数组链接:
+
+```java
+// serialization/Worm.java
+// Demonstrates object serialization
+import java.io.*;
+import java.util.*;
+class Data implements Serializable {
+ private int n;
+ Data(int n) { this.n = n; }
+ @Override
+ public String toString() {
+ return Integer.toString(n);
+ }
+}
+public class Worm implements Serializable {
+ private static Random rand = new Random(47);
+ private Data[] d = {
+ new Data(rand.nextInt(10)),
+ new Data(rand.nextInt(10)),
+ new Data(rand.nextInt(10))
+ };
+ private Worm next;
+ private char c;
+ // Value of i == number of segments
+ public Worm(int i, char x) {
+ System.out.println("Worm constructor: " + i);
+ c = x;
+ if(--i > 0)
+ next = new Worm(i, (char)(x + 1));
+ }
+ public Worm() {
+ System.out.println("No-arg constructor");
+ }
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(":");
+ result.append(c);
+ result.append("(");
+ for(Data dat : d)
+ result.append(dat);
+ result.append(")");
+ if(next != null)
+ result.append(next);
+ return result.toString();
+ }
+ public static void
+ main(String[] args) throws ClassNotFoundException,
+ IOException {
+ Worm w = new Worm(6, 'a');
+ System.out.println("w = " + w);
+ try(
+ ObjectOutputStream out = new ObjectOutputStream(
+ new FileOutputStream("worm.dat"))
+ ) {
+ out.writeObject("Worm storage\n");
+ out.writeObject(w);
+ }
+ try(
+ ObjectInputStream in = new ObjectInputStream(
+ new FileInputStream("worm.dat"))
+ ) {
+ String s = (String)in.readObject();
+ Worm w2 = (Worm)in.readObject();
+ System.out.println(s + "w2 = " + w2);
+ }
+ try(
+ ByteArrayOutputStream bout =
+ new ByteArrayOutputStream();
+ ObjectOutputStream out2 =
+ new ObjectOutputStream(bout)
+ ) {
+ out2.writeObject("Worm storage\n");
+ out2.writeObject(w);
+ out2.flush();
+ try(
+ ObjectInputStream in2 = new ObjectInputStream(
+ new ByteArrayInputStream(
+ bout.toByteArray()))
+ ) {
+ String s = (String)in2.readObject();
+ Worm w3 = (Worm)in2.readObject();
+ System.out.println(s + "w3 = " + w3);
+ }
+ }
+ }
+}
+```
+
+输出为:
+
+```
+Worm constructor: 6
+Worm constructor: 5
+Worm constructor: 4
+Worm constructor: 3
+Worm constructor: 2
+Worm constructor: 1
+w = :a(853):b(119):c(802):d(788):e(199):f(881)
+Worm storage
+w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
+Worm storage
+w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
+```
+
+更有趣的是,Worm 内的 Data 对象数组是用随机数初始化的(这样就不用怀疑编译器保留了某种原始信息),每个 Worm 段都用一个 char 加以标记。该 char 是在递归生成链接的 Worm 列表时自动产生的。要创建一个 Worm,必须告诉构造器你所希望的它的长度。在产生下一个引用时,要调用 Worm 构造器,并将长度减 1,以此类推。最后一个 next 引用则为 null(空),表示已到达 Worm 的尾部
+
+以上这些操作都使得事情变得更加复杂,从而加大了对象序列化的难度。然而,真正的序列化过程却是非常简单的。一旦从另外某个流创建了 ObjectOutputstream,writeObject() 就会将对象序列化。注意也可以为一个 String 调用 writeObject() 也可以用与 DataOutputStream 相同的方法写人所有基本数据类型(它们具有同样的接口)。
+
+有两段看起来相似的独立的代码。一个读写的是文件,而另一个读写的是字节数组(ByteArray),可利用序列化将对象读写到任何 DatalnputStream 或者 DataOutputStream。
+
+从输出中可以看出,被还原后的对象确实包含了原对象中的所有链接。
+
+注意在对一个 Serializable 对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。整个对象都是通过从 InputStream 中取得数据恢复而来的。
+
## 查找类
+你或许会奇怪,将一个对象从它的序列化状态中恢复出来,有哪些工作是必须的呢?举个例子来说,假如我们将一个对象序列化,并通过网络将其作为文件传送给另一台计算机,那么,另一台计算机上的程序可以只利用该文件内容来还原这个对象吗?
+
+回答这个问题的最好方法就是做一个实验。下面这个文件位于本章的子目录下:
+
+```java
+// serialization/Alien.java
+// A serializable class
+import java.io.*;
+public class Alien implements Serializable {}
+```
+
+而用于创建和序列化一个 Alien 对象的文件也位于相同的目录下:
+
+```java
+// serialization/FreezeAlien.java
+// Create a serialized output file
+import java.io.*;
+public class FreezeAlien {
+ public static void main(String[] args) throws Exception {
+ try(
+ ObjectOutputStream out = new ObjectOutputStream(
+ new FileOutputStream("X.file"));
+ ) {
+ Alien quellek = new Alien();
+ out.writeObject(quellek);
+ }
+ }
+}
+```
+
+一旦该程序被编译和运行,它就会在 c12 目录下产生一个名为 X.file 的文件。以下代码位于一个名为 xiles 的子目录下:
+
+```java
+// serialization/xfiles/ThawAlien.java
+// Recover a serialized file
+// {java serialization.xfiles.ThawAlien}
+// {RunFirst: FreezeAlien}
+package serialization.xfiles;
+import java.io.*;
+public class ThawAlien {
+ public static void main(String[] args) throws Exception {
+ ObjectInputStream in = new ObjectInputStream(
+ new FileInputStream(new File("X.file")));
+ Object mystery = in.readObject();
+ System.out.println(mystery.getClass());
+ }
+}
+```
+
+输出为:
+
+```java
+class Alien
+```
+
+为了正常运行,必须保证 Java 虚拟机能找到相关的.class 文件。
+
## 控制序列化
+正如大家所看到的,默认的序列化机制并不难操纵。然而,如果有特殊的需要那又该怎么办呢?例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化。
+
+在这些特殊情况下,可通过实现 Externalizable 接口——代替实现 Serializable 接口-来对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal0 和 readExternal0。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。
+
+下面这个例子展示了 Externalizable 接口方法的简单实现。注意 Blip1 和 Blip2 除了细微的差别之外,几乎完全一致(研究一下代码,看看你能否发现):
+
+```java
+// serialization/Blips.java
+// Simple use of Externalizable & a pitfall
+import java.io.*;
+class Blip1 implements Externalizable {
+ public Blip1() {
+ System.out.println("Blip1 Constructor");
+ }
+ @Override
+ public void writeExternal(ObjectOutput out)
+ throws IOException {
+ System.out.println("Blip1.writeExternal");
+ }
+ @Override
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException {
+ System.out.println("Blip1.readExternal");
+ }
+}
+class Blip2 implements Externalizable {
+ Blip2() {
+ System.out.println("Blip2 Constructor");
+ }
+ @Override
+ public void writeExternal(ObjectOutput out)
+ throws IOException {
+ System.out.println("Blip2.writeExternal");
+ }
+ @Override
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException {
+ System.out.println("Blip2.readExternal");
+ }
+}
+public class Blips {
+ public static void main(String[] args) {
+ System.out.println("Constructing objects:");
+ Blip1 b1 = new Blip1();
+ Blip2 b2 = new Blip2();
+ try(
+ ObjectOutputStream o = new ObjectOutputStream(
+ new FileOutputStream("Blips.serialized"))
+ ) {
+ System.out.println("Saving objects:");
+ o.writeObject(b1);
+ o.writeObject(b2);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ // Now get them back:
+ System.out.println("Recovering b1:");
+ try(
+ ObjectInputStream in = new ObjectInputStream(
+ new FileInputStream("Blips.serialized"))
+ ) {
+ b1 = (Blip1)in.readObject();
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ // OOPS! Throws an exception:
+ //- System.out.println("Recovering b2:");
+ //- b2 = (Blip2)in.readObject();
+ }
+}
+```
+
+输出为:
+
+```
+Constructing objects:
+Blip1 Constructor
+Blip2 Constructor
+Saving objects:
+Blip1.writeExternal
+Blip2.writeExternal
+Recovering b1:
+Blip1 Constructor
+Blip1.readExternal
+```
+
+没有恢复 Blip2 对象的原因是那样做会导致一个异常。你找出 Blip1 和 Blip2 之间的区别了吗?Blipl 的构造器是“公共的”(pablic),Blip2 的构造器却不是,这样就会在恢复时造成异常。试试将 Blip2 的构造器变成 public 的,然后删除//注释标记,看看是否能得到正确的结果。
+
+恢复 b1 后,会调用 Blip1 默认构造器。这与恢复一个 Serializable 对象不同。对于 Serializable 对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个 Externalizable 对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用 readExternal() 必须注意这一点--所有默认的构造器都会被调用,才能使 Externalizable 对象产生正确的行为。
+
+下面这个例子示范了如何完整保存和恢复一个 Externalizable 对象:
+
+```java
+// serialization/Blip3.java
+// Reconstructing an externalizable object
+import java.io.*;
+public class Blip3 implements Externalizable {
+ private int i;
+ private String s; // No initialization
+ public Blip3() {
+ System.out.println("Blip3 Constructor");
+// s, i not initialized
+ }
+ public Blip3(String x, int a) {
+ System.out.println("Blip3(String x, int a)");
+ s = x;
+ i = a;
+// s & i initialized only in non-no-arg constructor.
+ }
+ @Override
+ public String toString() { return s + i; }
+ @Override
+ public void writeExternal(ObjectOutput out)
+ throws IOException {
+ System.out.println("Blip3.writeExternal");
+// You must do this:
+ out.writeObject(s);
+ out.writeInt(i);
+ }
+ @Override
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException {
+ System.out.println("Blip3.readExternal");
+// You must do this:
+ s = (String)in.readObject();
+ i = in.readInt();
+ }
+ public static void main(String[] args) {
+ System.out.println("Constructing objects:");
+ Blip3 b3 = new Blip3("A String ", 47);
+ System.out.println(b3);
+ try(
+ ObjectOutputStream o = new ObjectOutputStream(
+ new FileOutputStream("Blip3.serialized"))
+ ) {
+ System.out.println("Saving object:");
+ o.writeObject(b3);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+// Now get it back:
+ System.out.println("Recovering b3:");
+ try(
+ ObjectInputStream in = new ObjectInputStream(
+ new FileInputStream("Blip3.serialized"))
+ ) {
+ b3 = (Blip3)in.readObject();
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println(b3);
+ }
+}
+```
+
+输出为:
+
+```
+Constructing objects:
+Blip3(String x, int a)
+A String 47
+Saving object:
+Blip3.writeExternal
+Recovering b3:
+Blip3 Constructor
+Blip3.readExternal
+A String 47
+```
+
+其中,字段 s 和只在第二个构造器中初始化,而不是在默认的构造器中初始化。这意味着假如不在 readExternal0 中初始化 s 和 i,s 就会为 null,而就会为零(因为在创建对象的第一步中将对象的存储空间清理为 0)。如果注释掉跟随于"You must do this”后面的两行代码,然后运行程序,就会发现当对象被还原后,s 是 null,而 i 是零。
+
+我们如果从一个 Externalizable 对象继承,通常需要调用基类版本的 writeExternal() 和 readExternal() 来为基类组件提供恰当的存储和恢复功能。
+
+因此,为了正常运行,我们不仅需要在 writeExternal() 方法(没有任何默认行为来为 Externalizable 对象写入任何成员对象)中将来自对象的重要信息写入,还必须在 readExternal() 方法中恢复数据。起先,可能会有一点迷惑,因为 Externalizable 对象的默认构造行为使其看起来似乎像某种自动发生的存储与恢复操作。但实际上并非如此。
+
+### transient 关键字
+
+当我们对序列化进行控制时,可能某个特定子对象不想让 Java 的序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况。即使对象中的这些信息是 private(私有)属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问到它。
+
+有一种办法可防止对象的敏感部分被序列化,就是将类实现为 Externalizable,如前面所示。这样一来,没有任何东西可以自动序列化,并且可以在 writeExternal() 内部只对所需部分进行显式的序列化。
+
+然而,如果我们正在操作的是一个 Seralizable 对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用 transient(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据——我自己会处理的"。
+
+例如,假设某个 Logon 对象保存某个特定的登录会话信息,登录的合法性通过校验之后,我们想把数据保存下来,但不包括密码。为做到这一点,最简单的办法是实现 Serializable,并将 password 字段标志为 transient,下面是具体的代码:
+
+```java
+// serialization/Logon.java
+// Demonstrates the "transient" keyword
+import java.util.concurrent.*;
+import java.io.*;
+import java.util.*;
+import onjava.Nap;
+public class Logon implements Serializable {
+ private Date date = new Date();
+ private String username;
+ private transient String password;
+ public Logon(String name, String pwd) {
+ username = name;
+ password = pwd;
+ }
+ @Override
+ public String toString() {
+ return "logon info: \n username: " +
+ username + "\n date: " + date +
+ "\n password: " + password;
+ }
+ public static void main(String[] args) {
+ Logon a = new Logon("Hulk", "myLittlePony");
+ System.out.println("logon a = " + a);
+ try(
+ ObjectOutputStream o =
+ new ObjectOutputStream(
+ new FileOutputStream("Logon.dat"))
+ ) {
+ o.writeObject(a);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ new Nap(1);
+// Now get them back:
+ try(
+ ObjectInputStream in = new ObjectInputStream(
+ new FileInputStream("Logon.dat"))
+ ) {
+ System.out.println(
+ "Recovering object at " + new Date());
+ a = (Logon)in.readObject();
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println("logon a = " + a);
+ }
+}
+```
+
+输出为:
+
+```
+logon a = logon info:
+username: Hulk
+date: Tue May 09 06:07:47 MDT 2017
+password: myLittlePony
+Recovering object at Tue May 09 06:07:49 MDT 2017
+logon a = logon info:
+username: Hulk
+date: Tue May 09 06:07:47 MDT 2017
+password: null
+```
+
+可以看到,其中的 date 和 username 是一般的(不是 transient 的),所以它们会被自动序列化。而 password 是 transient 的,所以不会被自动保存到磁盘;另外,自动序列化机制也不会尝试去恢复它。当对象被恢复时,password 就会变成 null。注意,虽然 toString() 是用重载后的+运算符来连接 String 对象,但是 null 引用会被自动转换成字符串 null。
+
+我们还可以发现:date 字段被存储了到磁盘并从磁盘上被恢复了出来,而且没有再重新生成。由于 Externalizable 对象在默认情况下不保存它们的任何字段,所以 transient 关键字只能和 Serializable 对象一起使用。
+
+### Externalizable 的替代方法
+
+如果不是特别坚持实现 Externalizable 接口,那么还有另一种方法。我们可以实现 Serializable 接口,并添加(注意我说的是“添加”,而非“覆盖”或者“实现”)名为 writeObject() 和 readObject() 的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制。
+
+这些方法必须具有准确的方法特征签名:
+
+```java
+private void writeObject(ObjectOutputStream stream) throws IOException
+
+private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
+```
+
+从设计的观点来看,现在事情变得真是不可思议。首先,我们可能会认为由于这些方法不是基类或者 Serializable 接口的一部分,所以应该在它们自己的接口中进行定义。但是注意它们被定义成了 private,这意味着它们仅能被这个类的其他成员调用。然而,实际上我们并没有从这个类的其他方法中调用它们,而是 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject() 和 readobject() 方法调用你的对象的 writeObject() 和 readObject() 方法(注意关于这里用到的相同方法名,我尽量抑制住不去谩骂。一句话:混乱)。读者可能想知道 ObjectOutputStream 和 ObjectInputStream 对象是怎样访问你的类中的 private 方法的。我们只能假设这正是序列化神奇的一部分。
+
+在接口中定义的所有东西都自动是 public 的,因此如果 writeObject() 和 readObject() 必须是 private 的,那么它们不会是接口的一部分。因为我们必须要完全遵循其方法特征签名,所以其效果就和实现了接口一样。
+
+在调用 ObjectOutputStream.writeObject() 时,会检查所传递的 Serializable 对象,看看是否实现了它自己的 writeObject()。如果是这样,就跳过正常的序列化过程并调用它的 writeObiect()。readObject() 的情形与此相同。
+
+还有另外一个技巧。在你的 writeObject() 内部,可以调用 defaultWriteObject() 来选择执行默认的 writeObject()。类似地,在 readObject() 内部,我们可以调用 defaultReadObject(),下面这个简单的例子演示了如何对一个 Serializable 对象的存储与恢复进行控制:
+
+```java
+// serialization/SerialCtl.java
+// Controlling serialization by adding your own
+// writeObject() and readObject() methods
+import java.io.*;
+public class SerialCtl implements Serializable {
+ private String a;
+ private transient String b;
+ public SerialCtl(String aa, String bb) {
+ a = "Not Transient: " + aa;
+ b = "Transient: " + bb;
+ }
+ @Override
+ public String toString() { return a + "\n" + b; }
+ private void writeObject(ObjectOutputStream stream)
+ throws IOException {
+ stream.defaultWriteObject();
+ stream.writeObject(b);
+ }
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException {
+ stream.defaultReadObject();
+ b = (String)stream.readObject();
+ }
+ public static void main(String[] args) {
+ SerialCtl sc = new SerialCtl("Test1", "Test2");
+ System.out.println("Before:\n" + sc);
+ try (
+ ByteArrayOutputStream buf =
+ new ByteArrayOutputStream();
+ ObjectOutputStream o =
+ new ObjectOutputStream(buf);
+ ) {
+ o.writeObject(sc);
+// Now get it back:
+ try (
+ ObjectInputStream in =
+ new ObjectInputStream(
+ new ByteArrayInputStream(
+ buf.toByteArray()));
+ ) {
+ SerialCtl sc2 = (SerialCtl)in.readObject();
+ System.out.println("After:\n" + sc2);
+ }
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+
+输出为:
+
+```
+Before:
+Not Transient: Test1
+Transient: Test2
+After:
+Not Transient: Test1
+Transient: Test2
+```
+
+在这个例子中,有一个 String 字段是普通字段,而另一个是 transient 字段,用来证明非 transient 字段由 defaultWriteObject() 方法保存,而 transient 字段必须在程序中明确保存和恢复。字段是在构造器内部而不是在定义处进行初始化的,以此可以证实它们在反序列化还原期间没有被一些自动化机制初始化。
+
+如果我们打算使用默认机制写入对象的非 transient 部分,那么必须调用 defaultwriteObject() 作为 writeObject() 中的第一个操作,并让 defaultReadObject() 作为 readObject() 中的第一个操作。这些都是奇怪的方法调用。例如,如果我们正在为 ObjectOutputStream 调用 defaultWriteObject() 且没有传递任何参数,然而不知何故它却可以运行,并且知道对象的引用以及如何写入非 transient 部分。真是奇怪之极。
+
+对 transient 对象的存储和恢复使用了我们比较熟悉的代码。请再考虑一下在这里所发生的事情。在 main0)中,创建 SerialCtl 对象,然后将其序列化到 ObjectOutputStream(注意在这种情况下,使用的是缓冲区而不是文件-这对于 ObjectOutputStream 来说是完全一样的)。序列化发生在下面这行代码当中
+
+```java
+o.writeObject(sc);
+```
+
+writeObject() 方法必须检查 sc,判断它是否拥有自己的 writeObject() 方法(不是检查接口——这里根本就没有接口,也不是检查类的类型,而是利用反射来真正地搜索方法)。如果有,那么就会使用它。对 readObject() 也采用了类似的方法。或许这是解决这个问题的唯一切实可行的方法,但它确实有点古怪。
+
+### 版本控制
+
+有时可能想要改变可序列化类的版本(比如源类的对象可能保存在数据库中)。虽然 Java 支持这种做法,但是你可能只在特殊的情况下才这样做,此外,还需要对它有相当深程度的了解(在这里我们就不再试图达到这一点)。从 http://java.oracle.com 下的 JDK 文档中对这一主题进行了非常彻底的论述。
+
## 使用持久化
+个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象-它们都具有指向第三个对象的引用-进行序列化,会发生什么情况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢?
+
+下面这个例子说明了上述问题:
+
+```java
+// serialization/MyWorld.java
+import java.io.*;
+import java.util.*;
+class House implements Serializable {}
+class Animal implements Serializable {
+ private String name;
+ private House preferredHouse;
+ Animal(String nm, House h) {
+ name = nm;
+ preferredHouse = h;
+ }
+ @Override
+ public String toString() {
+ return name + "[" + super.toString() +
+ "], " + preferredHouse + "\n";
+ }
+}
+public class MyWorld {
+ public static void main(String[] args) {
+ House house = new House();
+ List animals = new ArrayList<>();
+ animals.add(
+ new Animal("Bosco the dog", house));
+ animals.add(
+ new Animal("Ralph the hamster", house));
+ animals.add(
+ new Animal("Molly the cat", house));
+ System.out.println("animals: " + animals);
+ try(
+ ByteArrayOutputStream buf1 =
+ new ByteArrayOutputStream();
+ ObjectOutputStream o1 =
+ new ObjectOutputStream(buf1)
+ ) {
+ o1.writeObject(animals);
+ o1.writeObject(animals); // Write a 2nd set
+// Write to a different stream:
+ try(
+ ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
+ ObjectOutputStream o2 = new ObjectOutputStream(buf2)
+ ) {
+ o2.writeObject(animals);
+// Now get them back:
+ try(
+ ObjectInputStream in1 =
+ new ObjectInputStream(
+ new ByteArrayInputStream(
+ buf1.toByteArray()));
+ ObjectInputStream in2 =
+ new ObjectInputStream(
+ new ByteArrayInputStream(
+ buf2.toByteArray()))
+ ) {
+ List
+ animals1 = (List)in1.readObject(),
+ animals2 = (List)in1.readObject(),
+ animals3 = (List)in2.readObject();
+ System.out.println(
+ "animals1: " + animals1);
+ System.out.println(
+ "animals2: " + animals2);
+ System.out.println(
+ "animals3: " + animals3);
+ }
+ }
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+
+输出为:
+
+```
+animals: [Bosco the dog[Animal@15db9742],
+House@6d06d69c
+, Ralph the hamster[Animal@7852e922], House@6d06d69c
+, Molly the cat[Animal@4e25154f], House@6d06d69c
+]
+animals1: [Bosco the dog[Animal@7ba4f24f],
+House@3b9a45b3
+, Ralph the hamster[Animal@7699a589], House@3b9a45b3
+, Molly the cat[Animal@58372a00], House@3b9a45b3
+]
+animals2: [Bosco the dog[Animal@7ba4f24f],
+House@3b9a45b3
+, Ralph the hamster[Animal@7699a589], House@3b9a45b3
+, Molly the cat[Animal@58372a00], House@3b9a45b3
+]
+animals3: [Bosco the dog[Animal@4dd8dc3],
+House@6d03e736
+, Ralph the hamster[Animal@568db2f2], House@6d03e736
+, Molly the cat[Animal@378bf509], House@6d03e736
+]
+```
+
+
+
+这里有一件有趣的事:我们可以通过一个字节数组来使用对象序列化,从而实现对任何可 Serializable 对象的“深度复制"(deep copy)—— 深度复制意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。复制对象将在本书的 [附录:传递和返回对象 ]() 一章中进行深入地探讨。
+
+在这个例子中,Animal 对象包含有 House 类型的字段。在 main() 方法中,创建了一个 Animal 列表并将其两次序列化,分别送至不同的流。当其被反序列化还原并被打印时,我们可以看到所示的执行某次运行后的结果(每次运行时,对象将会处在不同的内存地址)。
+
+当然,我们期望这些反序列化还原后的对象地址与原来的地址不同。但请注意,在 animals1 和 animals2 中却出现了相同的地址,包括二者共享的那个指向 House 对象的引用。另一方面,当恢复 animals3 时,系统无法知道另一个流内的对象是第一个流内的对象的别名,因此它会产生出完全不同的对象网。
+
+只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事,无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),它们都可以被写出。
+
+最安全的做法是将其作为“原子”操作进行序列化。如果我们序列化了某些东西,再去做其他一些工作,再来序列化更多的东西,如此等等,那么将无法安全地保存系统状态。取而代之的是,将构成系统状态的所有对象都置入单一容器内,并在一个操作中将该容器直接写出。然后同样只需一次方法调用,即可以将其恢复。
+
+下面这个例子是一个想象的计算机辅助设计(CAD)系统,该例演示了这一方法。此外,它还引入了 static 字段的问题:如果我们查看 JDK 文档,就会发现 Class 是 Serializable 的,因此只需直接对 Class 对象序列化,就可以很容易地保存 static 字段。在任何情况下,这都是一种明智的做法。
+
+```java
+// serialization/AStoreCADState.java
+// Saving the state of a fictitious CAD system
+import java.io.*;
+import java.util.*;
+import java.util.stream.*;
+enum Color { RED, BLUE, GREEN }
+abstract class Shape implements Serializable {
+ private int xPos, yPos, dimension;
+ private static Random rand = new Random(47);
+ private static int counter = 0;
+ public abstract void setColor(Color newColor);
+ public abstract Color getColor();
+ Shape(int xVal, int yVal, int dim) {
+ xPos = xVal;
+ yPos = yVal;
+ dimension = dim;
+ }
+ public String toString() {
+ return getClass() + "color[" + getColor() +
+ "] xPos[" + xPos + "] yPos[" + yPos +
+ "] dim[" + dimension + "]\n";
+ }
+ public static Shape randomFactory() {
+ int xVal = rand.nextInt(100);
+ int yVal = rand.nextInt(100);
+ int dim = rand.nextInt(100);
+ switch(counter++ % 3) {
+ default:
+ case 0: return new Circle(xVal, yVal, dim);
+ case 1: return new Square(xVal, yVal, dim);
+ case 2: return new Line(xVal, yVal, dim);
+ }
+ }
+}
+class Circle extends Shape {
+ private static Color color = Color.RED;
+ Circle(int xVal, int yVal, int dim) {
+ super(xVal, yVal, dim);
+ }
+ public void setColor(Color newColor) {
+ color = newColor;
+ }
+ public Color getColor() { return color; }
+}
+class Square extends Shape {
+ private static Color color = Color.RED;
+ Square(int xVal, int yVal, int dim) {
+ super(xVal, yVal, dim);
+ }
+ public void setColor(Color newColor) {
+ color = newColor;
+ }
+ public Color getColor() { return color; }
+}
+class Line extends Shape {
+ private static Color color = Color.RED;
+ public static void
+ serializeStaticState(ObjectOutputStream os)
+ throws IOException { os.writeObject(color); }
+ public static void
+ deserializeStaticState(ObjectInputStream os)
+ throws IOException, ClassNotFoundException {
+ color = (Color)os.readObject();
+ }
+ Line(int xVal, int yVal, int dim) {
+ super(xVal, yVal, dim);
+ }
+ public void setColor(Color newColor) {
+ color = newColor;
+ }
+ public Color getColor() { return color; }
+}
+public class AStoreCADState {
+ public static void main(String[] args) {
+ List> shapeTypes =
+ Arrays.asList(
+ Circle.class, Square.class, Line.class);
+ List shapes = IntStream.range(0, 10)
+ .mapToObj(i -> Shape.randomFactory())
+ .collect(Collectors.toList());
+ // Set all the static colors to GREEN:
+ shapes.forEach(s -> s.setColor(Color.GREEN));
+ // Save the state vector:
+ try(
+ ObjectOutputStream out =
+ new ObjectOutputStream(
+ new FileOutputStream("CADState.dat"))
+ ) {
+ out.writeObject(shapeTypes);
+ Line.serializeStaticState(out);
+ out.writeObject(shapes);
+ } catch(IOException e) {
+ throw new RuntimeException(e);
+ }
+ // Display the shapes:
+ System.out.println(shapes);
+ }
+}
+```
+
+输出为:
+
+```java
+[class Circlecolor[GREEN] xPos[58] yPos[55] dim[93]
+, class Squarecolor[GREEN] xPos[61] yPos[61] dim[29]
+, class Linecolor[GREEN] xPos[68] yPos[0] dim[22]
+, class Circlecolor[GREEN] xPos[7] yPos[88] dim[28]
+, class Squarecolor[GREEN] xPos[51] yPos[89] dim[9]
+, class Linecolor[GREEN] xPos[78] yPos[98] dim[61]
+, class Circlecolor[GREEN] xPos[20] yPos[58] dim[16]
+, class Squarecolor[GREEN] xPos[40] yPos[11] dim[22]
+, class Linecolor[GREEN] xPos[4] yPos[83] dim[6]
+, class Circlecolor[GREEN] xPos[75] yPos[10] dim[42]
+]
+```
+
+Shape 类实现了 Serializable,所以任何自 Shape 继承的类也都会自动是 Serializable 的。每个 Shape 都含有数据,而且每个派生自 Shape 的类都包含一个 static 字段,用来确定各种 Shape 类型的颜色(如果将 static 字段置入基类,只会产生一个 static 字段,因为 static 字段不能在派生类中复制)。可对基类中的方法进行重载,以便为不同的类型设置颜色(static 方法不会动态绑定,所以这些都是普通的方法)。每次调用 randomFactory() 方法时,它都会使用不同的随机数作为 Shape 的数据,从而创建不同的 Shape。
+
+在 main() 中,一个 ArrayList 用于保存 Class 对象,而另一个用于保存几何形状。
+
+恢复对象相当直观:
+
+```java
+// serialization/RecoverCADState.java
+// Restoring the state of the fictitious CAD system
+// {RunFirst: AStoreCADState}
+import java.io.*;
+import java.util.*;
+public class RecoverCADState {
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) {
+ try(
+ ObjectInputStream in =
+ new ObjectInputStream(
+ new FileInputStream("CADState.dat"))
+ ) {
+// Read in the same order they were written:
+ List> shapeTypes =
+ (List>)in.readObject();
+ Line.deserializeStaticState(in);
+ List shapes =
+ (List)in.readObject();
+ System.out.println(shapes);
+ } catch(IOException | ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+
+输出为:
+
+```java
+[class Circlecolor[RED] xPos[58] yPos[55] dim[93]
+, class Squarecolor[RED] xPos[61] yPos[61] dim[29]
+, class Linecolor[GREEN] xPos[68] yPos[0] dim[22]
+, class Circlecolor[RED] xPos[7] yPos[88] dim[28]
+, class Squarecolor[RED] xPos[51] yPos[89] dim[9]
+, class Linecolor[GREEN] xPos[78] yPos[98] dim[61]
+, class Circlecolor[RED] xPos[20] yPos[58] dim[16]
+, class Squarecolor[RED] xPos[40] yPos[11] dim[22]
+, class Linecolor[GREEN] xPos[4] yPos[83] dim[6]
+, class Circlecolor[RED] xPos[75] yPos[10] dim[42]
+]
+```
+
+可以看到,xPos,yPos 以及 dim 的值都被成功地保存和恢复了,但是对 static 信息的读取却出现了问题。所有读回的颜色应该都是“3”,但是真实情况却并非如此。Circle 的值为 1(定义为 RED),而 Square 的值为 0(记住,它们是在构造器中被初始化的)。看上去似乎 static 数据根本没有被序列化!确实如此——尽管 Class 类是 Serializable 的,但它却不能按我们所期望的方式运行。所以假如想序列化 static 值,必须自己动手去实现。
+
+这正是 Line 中的 serializeStaticState() 和 deserializeStaticState() 两个 static 方法的用途。可以看到,它们是作为存储和读取过程的一部分被显式地调用的。(注意必须维护写入序列化文件和从该文件中读回的顺序。)因此,为了使 CADStatejava 正确运转起来,你必须:
+
+1. 为几何形状添加 serializeStaticState() 和 deserializeStaticState()
+2. 移除 ArrayList shapeTypes 以及与之有关的所有代码。
+3. 在几何形状内添加对新的序列化和反序列化还原静态方法的调用。
+
+另一个要注意的问题是安全,因为序列化也会将 private 数据保存下来。如果你关心安全问题,那么应将其标记成 transient,但是这之后,还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些 private 变量。
+
+## XML
+
+对象序列化的一个重要限制是它只是 Java 的解决方案:只有 Java 程序才能反序列化这种对象。一种更具互操作性的解决方案是将数据转换为 XML 格式,这可以使其被各种各样的平台和语言使用。
+
+因为 XML 十分流行,所以用它来编程时的各种选择不胜枚举,包括随 JDK 发布的 javax.xml.*类库。我选择使用 Elliotte Rusty Harold 的开源 XOM 类库(可从 www.xom.nu 下载并获得文档),因为它看起来最简单,同时也是最直观的用 Java 产生和修改 XML 的方式。另外,XOM 还强调了 XML 的正确性。
+
+作为一个示例,假设有一个 APerson 对象,它包含姓和名,你想将它们序列化到 XML 中。下面的 APerson 类有一个 getXML() 方法,它使用 XOM 来产生被转换为 XML 的 Element 对象的 APerson 数据;还有一个构造器,接受 Element 并从中抽取恰当的 APerson 数据(注意,XML 示例都在它们自己的子目录中):
+
+```java
+// serialization/APerson.java
+// Use the XOM library to write and read XML
+// nu.xom.Node comes from http://www.xom.nu
+import nu.xom.*;
+import java.io.*;
+import java.util.*;
+public class APerson {
+ private String first, last;
+ public APerson(String first, String last) {
+ this.first = first;
+ this.last = last;
+ }
+ // Produce an XML Element from this APerson object:
+ public Element getXML() {
+ Element person = new Element("person");
+ Element firstName = new Element("first");
+ firstName.appendChild(first);
+ Element lastName = new Element("last");
+ lastName.appendChild(last);
+ person.appendChild(firstName);
+ person.appendChild(lastName);
+ return person;
+ }
+ // Constructor restores a APerson from XML:
+ public APerson(Element person) {
+ first = person
+ .getFirstChildElement("first").getValue();
+ last = person
+ .getFirstChildElement("last").getValue();
+ }
+ @Override
+ public String toString() {
+ return first + " " + last;
+ }
+ // Make it human-readable:
+ public static void
+ format(OutputStream os, Document doc)
+ throws Exception {
+ Serializer serializer =
+ new Serializer(os,"ISO-8859-1");
+ serializer.setIndent(4);
+ serializer.setMaxLength(60);
+ serializer.write(doc);
+ serializer.flush();
+ }
+ public static void main(String[] args) throws Exception {
+ List people = Arrays.asList(
+ new APerson("Dr. Bunsen", "Honeydew"),
+ new APerson("Gonzo", "The Great"),
+ new APerson("Phillip J.", "Fry"));
+ System.out.println(people);
+ Element root = new Element("people");
+ for(APerson p : people)
+ root.appendChild(p.getXML());
+ Document doc = new Document(root);
+ format(System.out, doc);
+ format(new BufferedOutputStream(
+ new FileOutputStream("People.xml")), doc);
+ }
+}
+```
+
+输出为:
+
+```xml
+[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
+
+
+
+ Dr. Bunsen
+ Honeydew
+
+
+ Gonzo
+ The Great
+
+
+ Phillip J.
+ Fry
+
+
+```
+
+XOM 的方法都具有相当的自解释性,可以在 XOM 文档中找到它们。XOM 还包含一个 Serializer 类,你可以在 format() 方法中看到它被用来将 XML 转换为更具可读性的格式。如果只调用 toXML(),那么所有东西都会混在一起,因此 Serializer 是一种便利工具。
+
+从 XML 文件中反序列化 Person 对象也很简单:
+
+
+```java
+// serialization/People.java
+// nu.xom.Node comes from http://www.xom.nu
+// {RunFirst: APerson}
+import nu.xom.*;
+import java.io.File;
+import java.util.*;
+public class People extends ArrayList {
+ public People(String fileName) throws Exception {
+ Document doc =
+ new Builder().build(new File(fileName));
+ Elements elements =
+ doc.getRootElement().getChildElements();
+ for(int i = 0; i < elements.size(); i++)
+ add(new APerson(elements.get(i)));
+ }
+ public static void main(String[] args) throws Exception {
+ People p = new People("People.xml");
+ System.out.println(p);
+ }
+}
+/* Output:
+[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
+*/
+```
+
+People 构造器使用 XOM 的 Builder.build() 方法打开并读取一个文件,而 getChildElements() 方法产生了一个 Elements 列表(不是标准的 Java List,只是一个拥有 size() 和 get() 方法的对象,因为 Harold 不想强制人们使用特定版本的 Java,但是仍旧希望使用类型安全的容器)。在这个列表中的每个 Element 都表示一个 Person 对象,因此它可以传递给第二个 Person 构造器。注意,这要求你提前知道 XML 文件的确切结构,但是这经常会有些问题。如果文件结构与你预期的结构不匹配,那么 XOM 将抛出异常。对你来说,如果你缺乏有关将来的 XML 结构的信息,那么就有可能会编写更复杂的代码去探测 XML 文档,而不是只对其做出假设。
+
+为了获取这些示例去编译它们,你必须将 XOM 发布包中的 JAR 文件放置到你的类路径中。
+
+这里只给出了用 Java 和 XOM 类库进行 XML 编程的简介,更详细的信息可以浏览 www.xom.nu 。
+
From fcbf42e75b19bc17b71ef557eb39e6de785ff77b Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Sun, 1 Sep 2019 15:15:15 +0800
Subject: [PATCH 074/449] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E5=AF=B9?=
=?UTF-8?q?=E8=B1=A1=E5=BA=8F=E5=88=97=E5=8C=96=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
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 51a798b7..04cc39ba 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@
- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md)
- [x] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md)
- [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md)
-- [ ] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md)
+- [x] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md)
- [ ] [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md)
- [x] [附录:C++和Java的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md)
- [ ] [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md)
From c289c8d880256112482199178c23fcc13770a553 Mon Sep 17 00:00:00 2001
From: Michael Liu
Date: Sun, 1 Sep 2019 20:00:22 +0800
Subject: [PATCH 075/449] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E6=B3=9B?=
=?UTF-8?q?=E5=9E=8B=E7=AE=80=E4=BB=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/20-Generics.md | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md
index 0ede2848..5ff6a03d 100644
--- a/docs/book/20-Generics.md
+++ b/docs/book/20-Generics.md
@@ -3,12 +3,38 @@
# 第二十章 泛型
+> 普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。
+
+多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了`final`类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。
+
+拘泥于单一的继承体系太过局限,因为只有继承体系中的对象才能适用基类作为参数的方法中。如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。这给予调用方一种选项,通过调整现有的类来实现接口,满足方法参数要求。接口可以突破继承体系的限制。
+
+即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。
+
+这就是泛型的概念,是 Java 5 的重大变化之一。泛型实现了*参数化类型*,这样你编写的组件(通常是集合)可以适用于多种类型。“泛型”这个术语的含义是“适用于很多类型”。编程语言中泛型出现的初衷是通过解耦类或方法与所使用的类型之间的约束,使得类或方法具备最宽泛的表达力。随后你会发现 Java 中泛型的实现并没有那么“泛”,你可能会质疑“泛型”这个词是否合适用来描述这一功能。
+
+如果你从未接触过参数化类型机制,你会发现泛型对 Java 语言确实是个很有益的补充。在你实例化一个类型参数时,编译器会负责转型并确保类型的正确性。这是一大进步。
+
+然而,如果你了解其他语言(例如 C++ )的参数化机制,你会发现,Java 泛型并不能满足所有的预期。使用别人创建好的泛型相对容易,但是创建自己的泛型时,就会遇到很多意料之外的麻烦。
+
+这并不是说 Java 泛型毫无用处。在很多情况下,它可以使代码更直接更优雅。不过,如果你见识过那种实现了更纯粹的泛型的编程语言,那么,Java 可能会令你失望。本章会介绍 Java 泛型的优点与局限。我会解释 Java 的泛型是如何发展成现在这样的,希望能够帮助你更有效地使用这个特性。
+
+### 与 C++ 的比较
+
+Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。
+
+但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好的做到所能做的(部分原因是,不必浪费时间在死胡同里)。
+
+第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。
+
+因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。
-## 简单泛型
+## 简单泛型
+
## 泛型接口
From e979950d4b20ccd64f441a4879dc5d59100f1852 Mon Sep 17 00:00:00 2001
From: Michael Liu
Date: Mon, 2 Sep 2019 22:03:11 +0800
Subject: [PATCH 076/449] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E7=AE=80?=
=?UTF-8?q?=E5=8D=95=E6=B3=9B=E5=9E=8B=E7=AC=AC=E4=B8=80=E9=83=A8=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/20-Generics.md | 89 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 88 insertions(+), 1 deletion(-)
diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md
index 5ff6a03d..6b0f5901 100644
--- a/docs/book/20-Generics.md
+++ b/docs/book/20-Generics.md
@@ -30,9 +30,96 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如
因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。
-
## 简单泛型
+促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。
+
+我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:
+
+```java
+// generics/Holder1.java
+
+class Automobile {}
+
+public class Holder1 {
+ private Automobile a;
+ public Holder1(Automobile a) { this.a = a; }
+ Automobile get() { return a; }
+}
+```
+
+这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到每个类型都编写一个新的类。
+
+在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象:
+
+```java
+// generics/ObjectHolder.java
+
+public class ObjectHolder {
+ private Object a;
+ public ObjectHolder(Object a) { this.a = a; }
+ public void set(Object a) { this.a = a; }
+ public Object get() { return a; }
+ public static void main(String[] args) {
+ ObjectHolder h2 = new ObjectHolder(new Automobile());
+ Automobile a = (Automobile)h2.get();
+ h2.set("Not an Automobile");
+ String s = (String)h2.get();
+ h2.set(1); // 自动装箱为 Integer
+ Integer x = (Integer)h2.get();
+ }
+}
+```
+
+现在,`ObjectHolder` 可以持有任何类型的对象,在上面的示例中,一个 `ObjectHolder` 先后持有了三种不同类型的对象。
+
+一个集合中存储多种不同类型的对象的情况很少见,通常而言,我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。
+
+因此,与其使用 `Object` ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用*类型参数*,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中,`T` 就是类型参数:
+
+```java
+// generics/GenericHolder.java
+
+public class GenericHolder {
+ private T a;
+ public GenericHolder() {}
+ public void set(T a) { this.a = a; }
+ public T get() { return a; }
+ public static void main(String[] args) {
+ GenericHolder h3 = new GenericHolder();
+ h3.set(new Automobile()); // 此处有类型校验
+ Automobile a = h3.get(); // 无需类型转换
+ //- h3.set("Not an Automobile"); // 报错
+ //- h3.set(1); // 报错
+ }
+}
+```
+
+创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。
+
+这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。
+
+你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子:
+
+```java
+// generics/Diamond.java
+
+class Bob {}
+
+public class Diamond {
+ public static void main(String[] args) {
+ GenericHolder h3 = new GenericHolder<>();
+ h3.set(new Bob());
+ }
+}
+```
+
+注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。
+
+一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。
+
+
+
## 泛型接口
From 02ca18a25c193fb1265705ae5ab8d5d20b4177b4 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Tue, 3 Sep 2019 21:08:50 +0800
Subject: [PATCH 077/449] Update 04-Operators.md
#237
---
docs/book/04-Operators.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index 2750e053..ebccf668 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -903,7 +903,7 @@ while(x = y) {
}
```
-显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无线循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。
+显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。
唯一有种情况例外:当变量 `x` 和 `y` 都是布尔值,例如 `x=y` 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。
@@ -1469,4 +1469,4 @@ bigger = -4
[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”
-
\ No newline at end of file
+
From f53707a3c9b2f87a7f24325432bf8607ede30b3c Mon Sep 17 00:00:00 2001
From: Michael Liu
Date: Tue, 3 Sep 2019 21:26:35 +0800
Subject: [PATCH 078/449] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E7=AE=80?=
=?UTF-8?q?=E5=8D=95=E6=B3=9B=E5=9E=8B=E5=B0=8F=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
翻译元组类库子小节
---
docs/book/20-Generics.md | 139 +++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md
index 6b0f5901..b1592835 100644
--- a/docs/book/20-Generics.md
+++ b/docs/book/20-Generics.md
@@ -118,7 +118,146 @@ public class Diamond {
一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。
+### 一个元组类库
+有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个容器对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。
+
+这个概念称为*元组*,它是将一组对象直接打包存储于其中的一个单一对象。可以从容器对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象* 或 *信使* )。
+
+通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明其类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组:
+
+```java
+// onjava/Tuple2.java
+package onjava;
+
+public class Tuple2 {
+ public final A a1;
+ public final B a2;
+ public Tuple2(A a, B b) { a1 = a; a2 = b; }
+ public String rep() { return a1 + ", " + a2; }
+ @Override
+ public String toString() {
+ return "(" + rep() + ")";
+ }
+}
+```
+
+构造函数传入要存储的对象。这个元组隐式地保持了其中元素的次序。
+
+初次阅读上面的代码时,你可能认为这违反了 Java 编程的封装原则。`a1` 和 `a2` 应该声明为 **private**,然后提供 `getFirst()` 和 `getSecond()` 取值方法才对呀?考虑下这样做能提供的“安全性”是什么:元组的使用程序可以读取 `a1` 和 `a2` 然后对它们执行任何操作,但无法对 `a1` 和 `a2` 重新赋值。例子中的 `final` 可以实现同样的效果,并且更为简洁明了。
+
+另一种设计思路是允许元组的用户给 `a1` 和 `a2` 重新赋值。然而,采用上例中的形式无疑更加安全,如果用户想存储不同的元素,就会强制他们创建新的 `Tuple2` 对象。
+
+我们可以利用继承机制实现长度更长的元组。添加更多的类型参数就行了:
+
+```java
+// onjava/Tuple3.java
+package onjava;
+
+public class Tuple3 extends Tuple2 {
+ public final C a3;
+ public Tuple3(A a, B b, C c) {
+ super(a, b);
+ a3 = c;
+ }
+ @Override
+ public String rep() {
+ return super.rep() + ", " + a3;
+ }
+}
+
+
+// onjava/Tuple4.java
+package onjava;
+
+public class Tuple4
+ extends Tuple3 {
+ public final D a4;
+ public Tuple4(A a, B b, C c, D d) {
+ super(a, b, c);
+ a4 = d;
+ }
+ @Override
+ public String rep() {
+ return super.rep() + ", " + a4;
+ }
+}
+
+
+// onjava/Tuple5.java
+package onjava;
+
+public class Tuple5
+ extends Tuple4 {
+ public final E a5;
+ public Tuple5(A a, B b, C c, D d, E e) {
+ super(a, b, c, d);
+ a5 = e;
+ }
+ @Override
+ public String rep() {
+ return super.rep() + ", " + a5;
+ }
+}
+```
+
+演示需要,再定义两个类:
+
+```java
+// generics/Amphibian.java
+public class Amphibian {}
+
+// generics/Vehicle.java
+public class Vehicle {}
+```
+
+使用元组时,你只需要定义一个长度适合的元组,将其作为返回值即可。注意下面例子中方法的返回类型:
+
+```java
+// generics/TupleTest.java
+import onjava.*;
+
+public class TupleTest {
+ static Tuple2 f() {
+ // 47 自动装箱为 Integer
+ return new Tuple2<>("hi", 47);
+ }
+
+ static Tuple3 g() {
+ return new Tuple3<>(new Amphibian(), "hi", 47);
+ }
+
+ static Tuple4 h() {
+ return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47);
+ }
+
+ static Tuple5 k() {
+ return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1);
+ }
+
+ public static void main(String[] args) {
+ Tuple2 ttsi = f();
+ System.out.println(ttsi);
+ // ttsi.a1 = "there"; // 编译错误,因为 final 不能重新赋值
+ System.out.println(g());
+ System.out.println(h());
+ System.out.println(k());
+ }
+}
+
+/* 输出:
+ (hi, 47)
+ (Amphibian@1540e19d, hi, 47)
+ (Vehicle@7f31245a, Amphibian@6d6f6e28, hi, 47)
+ (Vehicle@330bedb4, Amphibian@2503dbd3, hi, 47, 11.1)
+ */
+```
+
+有了泛型,你可以很容易地创建元组,令其返回一组任意类型的对象。
+
+通过 `ttsi.a1 = "there"` 语句的报错,我们可以看出,**final** 声明确实可以确保 **public** 字段在对象被构造出来之后就不能重新赋值了。
+
+在上面的程序中,`new` 表达式有些啰嗦。本章稍后会介绍,如何利用 *泛型方法* 简化它们。
From 41aa6297316ab2816afab3528fa26356174fd3ca Mon Sep 17 00:00:00 2001
From: Michael Liu
Date: Wed, 4 Sep 2019 21:52:02 +0800
Subject: [PATCH 079/449] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E7=AE=80?=
=?UTF-8?q?=E5=8D=95=E6=B3=9B=E5=9E=8B=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E5=AE=8C=E6=88=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
翻译 堆栈类 和 RandomList 子小节,自此简单泛型小节翻译完成。
---
docs/book/20-Generics.md | 99 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 99 insertions(+)
diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md
index b1592835..b87508fe 100644
--- a/docs/book/20-Generics.md
+++ b/docs/book/20-Generics.md
@@ -259,6 +259,105 @@ public class TupleTest {
在上面的程序中,`new` 表达式有些啰嗦。本章稍后会介绍,如何利用 *泛型方法* 简化它们。
+### 一个堆栈类
+
+接下来我们看一个稍微复杂一点的例子:堆栈。在[集合](./12-Collections.md)一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。
+
+这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。
+
+```java
+// generics/LinkedStack.java
+// 用链式结构实现的堆栈
+
+public class LinkedStack {
+ private static class Node {
+ U item;
+ Node next;
+
+ Node() { item = null; next = null; }
+ Node(U item, Node next) {
+ this.item = item;
+ this.next = next;
+ }
+
+ boolean end() {
+ return item == null && next == null;
+ }
+ }
+
+ private Node top = new Node<>(); // 栈顶
+
+ public void push(T item) {
+ top = new Node<>(item, top);
+ }
+
+ public T pop() {
+ T result = top.item;
+ if (!top.end()) {
+ top = top.next;
+ }
+ return result;
+ }
+
+ public static void main(String[] args) {
+ LinkedStack lss = new LinkedStack<>();
+ for (String s : "Phasers on stun!".split(" ")) {
+ lss.push(s);
+ }
+ String s;
+ while ((s = lss.pop()) != null) {
+ System.out.println(s);
+ }
+ }
+}
+```
+
+输出结果:
+
+```java
+stun!
+on
+Phasers
+```
+
+内部类 `Node` 也是一个泛型,它拥有自己的类型参数。
+
+这个例子使用了一个 *末端标识* (end sentinel) 来判断栈何时为空。这个末端标识是在构造 `LinkedStack` 时创建的。然后,每次调用 `push()` 就会创建一个 `Node` 对象,并将其链接到前一个 `Node` 对象。当你调用 `pop()` 方法时,总是返回 `top.item`,然后丢弃当前 `top` 所指向的 `Node`,并将 `top` 指向下一个 `Node`,除非到达末端标识,这时就不能再移动 `top` 了。如果已经到达末端,程序还继续调用 `pop()` 方法,它只能得到 `null`,说明栈已经空了。
+
+### RandomList
+
+作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用它的 `select()` 方法时都随机返回一个元素。如果希望这种列表可以适用于各种类型,就需要使用泛型:
+
+```java
+// generics/RandomList.java
+import java.util.*;
+import java.util.stream.*;
+
+public class RandomList extends ArrayList {
+ private Random rand = new Random(47);
+
+ public T select() {
+ return get(rand.nextInt(size()));
+ }
+
+ public static void main(String[] args) {
+ RandomList rs = new RandomList<>();
+ Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add);
+ IntStream.range(0, 11).forEach(i ->
+ System.out.print(rs.select() + " "));
+ );
+ }
+}
+```
+
+输出结果:
+
+```java
+brown over fox quick quick dog brown The brown lazy brown
+```
+
+`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。
+
## 泛型接口
From b71e7bb3c7c3041a34ddc0fd8a80aa3e38777ec1 Mon Sep 17 00:00:00 2001
From: Michael Liu
Date: Fri, 6 Sep 2019 21:31:39 +0800
Subject: [PATCH 080/449] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E6=B3=9B?=
=?UTF-8?q?=E5=9E=8B=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
泛型接口小节翻译完成
---
docs/book/20-Generics.md | 206 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 205 insertions(+), 1 deletion(-)
diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md
index b87508fe..984f4f34 100644
--- a/docs/book/20-Generics.md
+++ b/docs/book/20-Generics.md
@@ -359,11 +359,215 @@ brown over fox quick quick dog brown The brown lazy brown
`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。
-
## 泛型接口
+泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。
+
+一般而言,一个生成器只定义一个方法,用于创建对象。例如 `java.util.function` 类库中的 `Supplier` 就是一个生成器,调用其 `get()` 获取对象。`get()` 是泛型方法,返回值为类型参数 `T`。
+
+为了演示 `Supplier`,我们需要定义几个类。下面是个咖啡相关的继承体系:
+
+```java
+// generics/coffee/Coffee.java
+package generics.coffee;
+
+public class Coffee {
+ private static long counter = 0;
+ private final long id = counter++;
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " " + id;
+ }
+}
+
+
+// generics/coffee/Latte.java
+package generics.coffee;
+public class Latte extends Coffee {}
+
+
+// generics/coffee/Mocha.java
+package generics.coffee;
+public class Mocha extends Coffee {}
+
+
+// generics/coffee/Cappuccino.java
+package generics.coffee;
+public class Cappuccino extends Coffee {}
+
+
+// generics/coffee/Americano.java
+package generics.coffee;
+public class Americano extends Coffee {}
+
+
+// generics/coffee/Breve.java
+package generics.coffee;
+public class Breve extends Coffee {}
+```
+
+现在,我们可以编写一个类,实现 `Supplier` 接口,它能够随机生成不同类型的 `Coffee` 对象:
+
+```java
+// generics/coffee/CoffeeSupplier.java
+// {java generics.coffee.CoffeeSupplier}
+package generics.coffee;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+public class CoffeeSupplier
+implements Supplier, Iterable {
+ private Class>[] types = { Latte.class, Mocha.class,
+ Cappuccino.class, Americano.class, Breve.class };
+ private static Random rand = new Random(47);
+
+ public CoffeeSupplier() {}
+ // For iteration:
+ private int size = 0;
+ public CoffeeSupplier(int sz) { size = sz; }
+
+ @Override
+ public Coffee get() {
+ try {
+ return (Coffee) types[rand.nextInt(types.length)].newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ class CoffeeIterator implements Iterator {
+ int count = size;
+ @Override
+ public boolean hasNext() { return count > 0; }
+ @Override
+ public Coffee next() {
+ count--;
+ return CoffeeSupplier.this.get();
+ }
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new CoffeeIterator();
+ }
+
+ public static void main(String[] args) {
+ Stream.generate(new CoffeeSupplier())
+ .limit(5)
+ .forEach(System.out::println);
+ for (Coffee c : new CoffeeSupplier(5)) {
+ System.out.println(c);
+ }
+ }
+}
+```
+
+输出结果:
+
+```java
+Americano 0
+Latte 1
+Americano 2
+Mocha 3
+Mocha 4
+Breve 5
+Americano 6
+Latte 7
+Cappuccino 8
+Cappuccino 9
+```
+
+参数化的 `Supplier` 接口确保 `get()` 返回值是参数的类型。`CoffeeSupplier` 同时还实现了 `Iterable` 接口,所以能用于 *for-in* 语句。不过,它还需要知道何时终止循环,这正是第二个构造函数的作用。
+
+下面是另一个实现 `Supplier` 接口的例子,它负责生成 Fibonacci 数列:
+
+```java
+// generics/Fibonacci.java
+// Generate a Fibonacci sequence
+import java.util.function.*;
+import java.util.stream.*;
+
+public class Fibonacci implements Supplier {
+ private int count = 0;
+ @Override
+ public Integer get() { return fib(count++); }
+
+ private int fib(int n) {
+ if(n < 2) return 1;
+ return fib(n-2) + fib(n-1);
+ }
+
+ public static void main(String[] args) {
+ Stream.generate(new Fibonacci())
+ .limit(18)
+ .map(n -> n + " ")
+ .forEach(System.out::print);
+ }
+}
+```
+
+输出结果:
+
+```java
+1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
+```
+
+虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。
+
+如果还想更进一步,编写一个实现了 `Iterator` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterator` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。
+
+有多种方法可以实现适配器。例如,可以通过继承来创建适配器类:
+
+```java
+// generics/IterableFibonacci.java
+// Adapt the Fibonacci class to make it Iterable
+import java.util.*;
+
+public class IterableFibonacci
+extends Fibonacci implements Iterable {
+ private int n;
+ public IterableFibonacci(int count) { n = count; }
+
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ @Override
+ public boolean hasNext() { return n > 0; }
+ @Override
+ public Integer next() {
+ n--;
+ return IterableFibonacci.this.get();
+ }
+ @Override
+ public void remove() { // Not implemented
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ public static void main(String[] args) {
+ for(int i : new IterableFibonacci(18))
+ System.out.print(i + " ");
+ }
+}
+```
+
+输出结果:
+
+```java
+1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
+```
+
+在循环语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。
+
## 泛型方法
From d4fc1d7f1a83ceedafd8cd39b8632b0a163d58f5 Mon Sep 17 00:00:00 2001
From: StormZhao <1106009050@qq.com>
Date: Wed, 11 Sep 2019 13:14:32 +0800
Subject: [PATCH 081/449] Update 04-Operators.md
---
docs/book/04-Operators.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md
index ebccf668..8c949961 100644
--- a/docs/book/04-Operators.md
+++ b/docs/book/04-Operators.md
@@ -691,8 +691,7 @@ public class URShift {
```
11111111111111111111111111111111
1111111111111111111111
-1111111111111111111111111111111111111111111111111111111
-111111111
+1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
From 8e53689f74b8faa23344ccd349630ce2c11530e3 Mon Sep 17 00:00:00 2001
From: zhangzw
Date: Wed, 11 Sep 2019 21:35:29 +0800
Subject: [PATCH 082/449] =?UTF-8?q?=E9=83=A8=E5=88=86=E6=9B=B4=E6=96=B01?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/24-Concurrent-Programming.md | 162 +++++++++++++++++++++++--
1 file changed, 153 insertions(+), 9 deletions(-)
diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md
index 4bdd7294..d129610e 100644
--- a/docs/book/24-Concurrent-Programming.md
+++ b/docs/book/24-Concurrent-Programming.md
@@ -248,9 +248,9 @@ Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入
这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得您的生活比旧的替代品更加轻松。但是,您仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。
- Parallel Streams(并发流)
-到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在您对该语法(作为一个粉丝,我希望)感到满意,您可以获得额外的好处:您可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式
+到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在您对该语法(作为一个粉丝,我希望)感到满意,您可以获得额外的好处:您可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式
-添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。
+添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。
- 创建和运行任务
任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。您创建一个任务,然后将其交给Executorto运行。
@@ -666,7 +666,7 @@ current是使用线程安全的**AtomicInteger**类定义的,可以防止竞
试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用get()。添加limit(),你说“只需要这些。”基本上,当你将parallel()与limit()结合使用时,你要求随机输出 - 这可能对你正在解决的问题很好。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。
-什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示:
+什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示:
```java
// concurrent/ParallelStreamPuzzle3.java
@@ -702,7 +702,7 @@ public class ParallelStreamPuzzle3 {
为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。
-您还可以看到boxed()的添加,它接受int流并将其转换为Integer流。
+您还可以看到boxed()的添加,它接受int流并将其转换为Integer流。
现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。
@@ -860,7 +860,7 @@ NapTask[9] pool-1-thread-1
*/
```
-一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。
+一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。
```java
// concurrent/MoreTasksAfterShutdown.java
@@ -1235,7 +1235,7 @@ public class QuittableTask implements Runnable {
虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。
-- [1]:只要运行标志为true,此任务的run()方法将继续。
+- [1]:只要运行标志为true,此任务的run()方法将继续。
- [2]: 显示仅在任务退出时发生。
需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,您没有任何反馈来告诉您已经做错了。通常,您编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。
@@ -1267,7 +1267,7 @@ public class QuittingTasks {
*/
```
-我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。
+我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。
## CompletableFuture类
@@ -1428,8 +1428,150 @@ Machina0: complete
```
在这里,我们还添加了一个**Timer**,它向我们展示每一步增加100毫秒,还有一些额外的开销。
-**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。
+**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效[^5]。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。
+回调**thenApply()**开始一个操作,在这种情况下,在完成所有任务之前,不会完成**e CompletableFuture**的创建。虽然这有时很有用,但是启动所有任务通常更有价值,这样就可以运行时继续前进并执行其他操作。我们通过在操作结束时添加Async来实现此目的:
+
+```java
+// concurrent/CompletableApplyAsync.java
+import java.util.concurrent.*;
+import onjava.*;
+public class CompletableApplyAsync {
+ public static void main(String[] args) {
+ Timer timer = new Timer();
+ CompletableFuture cf =
+ CompletableFuture.completedFuture(
+ new Machina(0))
+ .thenApplyAsync(Machina::work)
+ .thenApplyAsync(Machina::work)
+ .thenApplyAsync(Machina::work)
+ .thenApplyAsync(Machina::work);
+ System.out.println(timer.duration());
+ System.out.println(cf.join());
+ System.out.println(timer.duration())
+ }
+}
+/* Output:
+116
+Machina0: ONE
+Machina0: TWO
+Machina0:THREE
+Machina0: complete
+Machina0: complete
+552
+*/
+```
+
+同步调用(我们通常使用得那种)意味着“当你完成工作时,返回”,而异步调用以意味着“立刻返回但是继续后台工作。”正如你所看到的,**cf**的创建现在发生得跟快。每次调用 **thenApplyAsync()** 都会立刻返回,因此可以进行下一次调用,整个链接序列的完成速度比以前快得快。
+
+事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了main()进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。
+
+这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将您需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。
+
+幸运的是,您需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,CompletableFuture为您管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。
+
+- 其他操作
+当您查看CompletableFuture的Javadoc时,您会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有thenApply(),thenApplyAsync()和thenApplyAsync()的第二种形式,它接受运行任务的Executor(在本书中我们忽略了Executor选项)。
+
+这是一个显示所有“基本”操作的示例,它们不涉及组合两个CompletableFutures或异常(我们将在稍后查看)。首先,我们将重复使用两个实用程序以提供简洁和方便:
+
+```java
+// concurrent/CompletableUtilities.java
+package onjava; import java.util.concurrent.*;
+public class CompletableUtilities {
+ // Get and show value stored in a CF:
+ public static void showr(CompletableFuture> c) {
+ try {
+ System.out.println(c.get());
+ } catch(InterruptedException
+ | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ // For CF operations that have no value:
+ public static void voidr(CompletableFuture c) {
+ try {
+ c.get(); // Returns void
+ } catch(InterruptedException
+ | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
+```
+
+showr()在CompletableFuture 上调用get()并显示结果,捕获两个可能的异常。voidr()是CompletableFuture 的showr()版本,即CompletableFutures,仅在任务完成或失败时显示。
+
+为简单起见,以下CompletableFutures只包装整数。cfi()是一个方便的方法,它在完成的CompletableFuture 中包装一个int:
+
+```java
+// concurrent/CompletableOperations.java
+import java.util.concurrent.*;
+import static onjava.CompletableUtilities.*;
+public class CompletableOperations {
+ static CompletableFuture cfi(int i) {
+ return CompletableFuture.completedFuture( Integer.valueOf(i));
+ }
+ public static void main(String[] args) {
+ showr(cfi(1)); // Basic test
+ voidr(cfi(2).runAsync(() ->
+ System.out.println("runAsync")));
+ voidr(cfi(3).thenRunAsync(() ->
+ System.out.println("thenRunAsync")));
+ voidr(CompletableFuture.runAsync(() ->
+ System.out.println("runAsync is static")));
+ showr(CompletableFuture.supplyAsync(() -> 99));
+ voidr(cfi(4).thenAcceptAsync(i ->
+ System.out.println("thenAcceptAsync: " + i)));
+ showr(cfi(5).thenApplyAsync(i -> i + 42));
+ showr(cfi(6).thenComposeAsync(i -> cfi(i + 99)));
+ CompletableFuture c = cfi(7);
+ c.obtrudeValue(111);
+ showr(c);
+ showr(cfi(8).toCompletableFuture());
+ c = new CompletableFuture<>();
+ c.complete(9);
+ showr(c);
+ c = new CompletableFuture<>();
+ c.cancel(true);
+ System.out.println("cancelled: " + c.isCancelled());
+ System.out.println("completed exceptionally: " +
+ c.isCompletedExceptionally());
+ System.out.println("done: " + c.isDone());
+ System.out.println(c);
+ c = new CompletableFuture<>();
+ System.out.println(c.getNow(777));
+ c = new CompletableFuture<>();
+ c.thenApplyAsync(i -> i + 42)
+ .thenApplyAsync(i -> i * 12);
+ System.out.println("dependents: " + c.getNumberOfDependents());
+ c.thenApplyAsync(i -> i / 2);
+ System.out.println("dependents: " + c.getNumberOfDependents());
+ }
+}
+/* Output:
+1
+runAsync
+thenRunAsync
+runAsync is static
+99
+thenAcceptAsync: 4
+47
+105
+111
+8
+9
+cancelled: true
+completed exceptionally: true
+done: true
+java.util.concurrent.CompletableFuture@6d311334[Complet ed exceptionally]
+777
+dependents: 1
+dependents: 2
+*/
+```
+
+main()包含一系列可由其int值引用的测试。cfi(1)演示了showr()正常工作。cfi(2)是调用runAsync()的示例。由于Runnable不产生返回值,因此结果是CompletableFuture ,因此使用voidr()。
## 死锁
@@ -1451,6 +1593,8 @@ Machina0: complete
[^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论
[^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。
-
+[^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为
+[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。
+
\ No newline at end of file
From b6a67c2c477fc14eb039fe5779a0696370e06971 Mon Sep 17 00:00:00 2001
From: FateNight
Date: Thu, 12 Sep 2019 16:23:32 +0800
Subject: [PATCH 083/449] Update 09-Polymorphism.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改笔误变量
---
docs/book/09-Polymorphism.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md
index a6093b4a..53e02e1f 100644
--- a/docs/book/09-Polymorphism.md
+++ b/docs/book/09-Polymorphism.md
@@ -585,7 +585,7 @@ public class FieldAccess {
输出:
```
-sup.field = 0, super.getField() = 1
+sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
```
From 6f936dffa1507f56aae07fe13effeb78bfa9ad1f Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Wed, 18 Sep 2019 21:33:29 +0800
Subject: [PATCH 084/449] 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 927861c5..a1f487a7 100644
--- a/docs/book/10-Interfaces.md
+++ b/docs/book/10-Interfaces.md
@@ -857,7 +857,7 @@ package interfaces.filters;
public class Waveform {
private static long counter;
- private final long id = count++;
+ private final long id = counter++;
@Override
public String toString() {
@@ -1041,7 +1041,7 @@ class FilterAdapter implements Processor {
}
}
-punlic class FilterProcessor {
+public class FilterProcessor {
public static void main(String[] args) {
Waveform w = new Waveform();
Applicator.apply(new FilterAdapter(new LowPass(1.0)), w);
From 0f81b3c2bce071fae87c9d277f5c474ca447063d Mon Sep 17 00:00:00 2001
From: xiangflight
Date: Tue, 17 Sep 2019 22:32:51 +0800
Subject: [PATCH 085/449] =?UTF-8?q?revision[14]=20=E9=98=B6=E6=AE=B5?=
=?UTF-8?q?=E6=80=A7=E6=A0=A1=E8=AE=A2=E5=AE=8C14=E7=AB=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/14-Streams.md | 72 ++++++++++++++++++++++-------------------
1 file changed, 39 insertions(+), 33 deletions(-)
diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md
index 6e5e3232..15c18adb 100644
--- a/docs/book/14-Streams.md
+++ b/docs/book/14-Streams.md
@@ -1135,9 +1135,9 @@ Not much of a cheese shop really
## Optional类
-在我们查看终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流为空时会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好的提示我们(也就是说,没有异常)?
+在我们学习终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)?
-**Optional** 可以实现这样的功能。首先确保准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。它们包括:
+**Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括:
- `findFirst()` 返回一个包含第一个元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**
- `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**
@@ -1147,7 +1147,7 @@ Not much of a cheese shop really
对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。
-以下是对空流进行的所有这些操作的简单测试:
+以下是对空流进行所有这些操作的简单测试:
```java
// streams/OptionalsFromEmptyStreams.java
@@ -1201,8 +1201,9 @@ import java.util.stream.*;
class OptionalBasics {
static void test(Optional optString) {
if(optString.isPresent())
- System.out.println(optString.get()); else
- System.out.println("Nothing inside!");
+ System.out.println(optString.get());
+ else
+ System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
@@ -1221,14 +1222,15 @@ Nothing inside!
当你接收到 **Optional** 对象时,应首先调用 `isPresent()` 检查其中是否包含元素。如果存在,可使用 `get()` 获取。
+
### 便利函数
有许多便利函数可以解包 **Optional** ,这简化了上述“对所包含的对象的检查和执行操作”的过程:
- `ifPresent(Consumer)`:当值存在时调用 **Consumer**,否则什么也不做。
- `orElse(otherObject)`:如果值存在则直接返回,否则生成 **otherObject**。
-- `orElseGet(Supplier)`:如果值存在直接生成对象,否则使用 **Supplier** 函数生成一个可替代对象。
-- `orElseThrow(Supplier)`:如果值存在直接生成对象,否则使用 **Supplier** 函数生成一个异常。
+- `orElseGet(Supplier)`:如果值存在则直接返回,否则使用 **Supplier** 函数生成一个可替代对象。
+- `orElseThrow(Supplier)`:如果值存在直接返回,否则使用 **Supplier** 函数生成一个异常。
如下是针对不同便利函数的简单演示:
@@ -1258,8 +1260,7 @@ public class Optionals {
try {
System.out.println(optString.orElseThrow(
() -> new Exception("Supplied")));
- }
- catch(Exception e) {
+ } catch(Exception e) {
System.out.println("Caught " + e);
}
}
@@ -1299,10 +1300,10 @@ Caught java.lang.Exception: Supplied
`test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。
-`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在[异常](./15-Exceptions.md) 这一章节中学习。
-
+`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在 [异常](./15-Exceptions.md) 这一章节中学习。
+
### 创建 Optional
当我们在自己的代码中加入 **Optional** 时,可以使用下面 3 个静态方法:
@@ -1363,7 +1364,7 @@ Null
- `flatMap(Function)`:同 `map()`,但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。
-以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时删除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例:
+以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时移除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例:
```java
@@ -1675,17 +1676,18 @@ Signal(dash)
Signal(dash)
```
-在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的行为。
-
+在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。
+
## 终端操作
-这些操作获取一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。
+这些操作接收一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。
-### 转化数组
+
+### 转化为数组
- `toArray()`:将流转换成适当类型的数组。
- `toArray(generator)`:在特殊情况下,生成器用于分配自定义的数组存储。
@@ -1704,17 +1706,17 @@ public class RandInts {
}
```
-上例将100个数值范围在0-10000之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。
+上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。
### 应用最终操作
-- `forEach(Consumer)`:常见的,如 `System.out::println` 作为 **Consumer** 函数。
+- `forEach(Consumer)`:你已经看到过很多次 `System.out::println` 作为 **Consumer** 函数。
- `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。
-第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问。这里简单介绍下`parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。这对内部循环是可行的。
+第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。
-`parallel()` 看似简单,实则棘手。更多内容,在稍后学习的[并发编程](24-Concurrent-Programming.md) 编程章节。
+`parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。
下例引入了 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:
@@ -1748,19 +1750,20 @@ public class ForEach {
258 555 693 861 961 429 868 200 522 207 288 128 551 589
```
-为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果熟悉均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。
+为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。
在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。
+
### 收集
- `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。
-- `collect(Supplier, BiConsumer, BiConsumer)`:同上,参数1 **Supplier** 创建了一个新结果集合,参数2 **BiConsumer** 将下一个元素包含到结果中,参数3 **BiConsumer** 用于将两个值组合起来。
+- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。
-在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现。可通过查看 `java.util.stream.Collectors` 部分的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。
+在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 `java.util.stream.Collectors` 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。
-假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过 `Collectors.toCollection()` 来为任何类型的集合提供构造函数引用。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例:
+假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过将集合的构造函数引用传递给 `Collectors.toCollection()`,从而构建任何类型的集合。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例:
```java
// streams/TreeSetOfWords.java
@@ -1795,7 +1798,7 @@ stream, streams, throws, toCollection, trim, util,
void, words2]
```
-**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。
+**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\W+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。
我们也可以在流中生成 **Map**。代码示例:
@@ -1846,7 +1849,7 @@ public class MapCollector {
{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}
```
-**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。如果我们能以某种方式组合两个流,那就再好不过了,但 Java 在这个问题上与我们斗争。所以我创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 方法允许我们在 `stream()` 方法中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。
+**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。如果能以某种方式组合两个流,那就再好不过了,但 Java 在这个问题上不如我们所愿。所以我创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 方法允许我们在 `stream()` 方法中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。
在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。
@@ -1880,11 +1883,12 @@ cheese
在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。
+
### 组合所有流元素
- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。
- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。
-- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显示的组合 `map()` 和 `reduce()` 来更简单的表达这一点。
+- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。
如下是一个用于演示 `reduce()` 的示例:
@@ -1943,10 +1947,10 @@ Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结
### 匹配
- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。
-- `anyMatch(Predicate)`:如果流中的一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
+- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
-你已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()` 和 `anyMatch()` 的用法基本上是等同的。让我们探究短路行为。为了创建消除冗余代码的 ` show()` 方法,我们必须首先发现如何概括地描述所有三个匹配器操作,然后将其转换为称为 **Matcher** 的接口:
+你已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()` 和 `anyMatch()` 的用法基本上是等同的。让我们探究短路行为。为了创建消除冗余代码的 ` show()` 方法,我们必须首先发现如何统一地描述所有三个匹配器操作,然后将其转换为称作 **Matcher** 的接口:
```java
// streams/Matching.java
@@ -1988,9 +1992,9 @@ public class Matching {
1 2 3 4 5 6 7 8 9 true
```
-**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数并且只返回 true 或者 false。它的第一个参数使我们要测试的流,第二个参数是一个谓词 **Predicate**。因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。
+**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数并且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。
-`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于像我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。
+`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。
### 元素查找
@@ -2053,9 +2057,10 @@ public class LastElement {
three
```
-`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字可选类型( numeric optional type),否则你使用的 **Optional** 类型为 `Optional`。
+`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字 **Optional** 类型( numeric optional type),否则使用 **Optional** 类型,就像上例中的 `Optional`。
+
### 信息
- `count()`:流中的元素个数。
@@ -2101,7 +2106,7 @@ you
- `average()` :求取流元素平均值。
- `max()` 和 `min()`:因为这些操作在数字流上面,所以不需要 **Comparator**。
- `sum()`:对所有流元素进行求和。
-- `summaryStatistics()`:生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,但是你可以直接使用方法产生所有的数据。
+- `summaryStatistics()`:生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,因为你可以使用直接的方法产生所有的数据。
```java
// streams/NumericStreamInfo.java
@@ -2135,4 +2140,5 @@ IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}
流改变并极大地提升了 Java 编程的性质,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流动。在本书的剩余部分,我们将尽可能地使用流。
+
From fc6290acd48eeae46e9bad11471c11067488cf54 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Thu, 19 Sep 2019 11:54:41 +0800
Subject: [PATCH 086/449] fix typo -> chapter 6
---
docs/book/06-Housekeeping.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md
index b589ee4f..ded064c6 100644
--- a/docs/book/06-Housekeeping.md
+++ b/docs/book/06-Housekeeping.md
@@ -1186,7 +1186,7 @@ Mugs(int)
new Mugs(1) completed
```
-看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持"匿名内部类"(参见"内部类"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,示例初始化子句是在两个构造器之前执行的。
+看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持"匿名内部类"(参见"内部类"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,实例初始化子句是在两个构造器之前执行的。
From 0cb74d1550a2415b4151e25514caa6b8d3004e99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=A6=E9=AA=8F?=
Date: Thu, 19 Sep 2019 22:31:20 +0800
Subject: [PATCH 087/449] =?UTF-8?q?=E5=B0=86field=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E4=BB=8E=E5=B1=9E=E6=80=A7=E6=94=B9=E4=B8=BA=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=EF=BC=8C=E7=9B=B8=E5=AF=B9=E6=9D=A5=E8=AF=B4=E5=AD=97=E6=AE=B5?=
=?UTF-8?q?=E6=9B=B4=E5=B8=B8=E7=94=A8=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/book/03-Objects-Everywhere.md | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md
index 8829e427..95cf3898 100644
--- a/docs/book/03-Objects-Everywhere.md
+++ b/docs/book/03-Objects-Everywhere.md
@@ -219,9 +219,9 @@ ATypeName a = new ATypeName();
到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。
-### 属性
+### 字段
-当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和属性(field)。类的属性可以是基本类型,也可以是引用类型。如果类的属性是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其属性的空间。通常,属性不在对象间共享。下面是一个具有某些属性的类的代码示例:
+当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和字段(field)。类的字段可以是基本类型,也可以是引用类型。如果类的字段是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其字段的空间。通常,字段不在对象间共享。下面是一个具有某些字段的类的代码示例:
```java
class DataOnly {
@@ -237,7 +237,7 @@ class DataOnly {
DataOnly data = new DataOnly();
```
-我们必须通过这个对象的引用来指定属性值。格式:对象名称.方法名称或属性名称。代码示例:
+我们必须通过这个对象的引用来指定字段值。格式:对象名称.方法名称或字段名称。代码示例:
```java
data.i = 47;
@@ -256,7 +256,7 @@ class DataOnly {
### 基本类型默认值
-如果类的成员变量(属性)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。
+如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。
| 基本类型 | 初始值 |
| :-----: |:-----: |
@@ -269,9 +269,9 @@ class DataOnly {
| float | 0.0f |
| double | 0.0d |
-这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的属性始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。
+这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。
-这种默认值的赋予并不适用于局部变量 —— 那些不属于类的属性的变量。 因此,若在方法中定义的基本类型数据,如下:
+这种默认值的赋予并不适用于局部变量 —— 那些不属于类的字段的变量。 因此,若在方法中定义的基本类型数据,如下:
```java
int x;
@@ -405,7 +405,7 @@ import java.util.*;
一些面向对象的语言使用类数据(class data)和类方法(class method),表示静态数据和方法只是作为类,而不是类的某个特定对象而存在的。有时 Java 文献也使用这些术语。
-我们可以在类的属性或方法前添加 `static` 关键字来表示这是一个静态属性或静态方法。 代码示例:
+我们可以在类的字段或方法前添加 `static` 关键字来表示这是一个静态字段或静态方法。 代码示例:
```java
class StaticTest {
@@ -428,7 +428,7 @@ StaticTest.i++;
`++` 运算符将会使变量结果 + 1。此时 `st1.i` 和 `st2.i` 的值都变成了 48。
-使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态属性或方法 [^7]。 代码示例:
+使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态字段或方法 [^7]。 代码示例:
```java
class Incrementable {
@@ -546,7 +546,7 @@ os.arch=amd64
java.io.tmpdir=C:\Users\Bruce\AppData\Local\Temp\
```
-`main()` 方法中的第一行会输出所有的系统属性,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的属性。该例中我们使用到了 **user.name** 和 **java.library.path**。
+`main()` 方法中的第一行会输出所有的系统字段,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的字段。该例中我们使用到了 **user.name** 和 **java.library.path**。
### 编译和运行
From b4c2c95fcb7210c08641dd2c3e66841b221472e1 Mon Sep 17 00:00:00 2001
From: withthewind
Date: Fri, 20 Sep 2019 10:34:28 +0800
Subject: [PATCH 088/449] Commit Appendix: Benefits and Costs of Static Type
Checking - Quote
---
.../book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md b/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md
index 071a3c32..e5d149fd 100644
--- a/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md
+++ b/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md
@@ -2,7 +2,7 @@
# 附录:静态语言类型检查
-
+> 这是一本我多年来撰写的经过编辑过的论文集,论文集试图将静态检查语言和动态语言之间的争论放到一个正确的角度。还有一个前言部分,描述了我最近对这个话题的思考和见解。
## 前言
From 9cf3a5c79e827c1db974c0fb2514ebaa7ad4c158 Mon Sep 17 00:00:00 2001
From: withthewind
Date: Fri, 20 Sep 2019 15:21:39 +0800
Subject: [PATCH 089/449] Commit Appendix: Javadoc - Opening Paragraph
---
docs/book/Appendix-Javadoc.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md
index 5ea99544..cc25c5b9 100644
--- a/docs/book/Appendix-Javadoc.md
+++ b/docs/book/Appendix-Javadoc.md
@@ -3,6 +3,17 @@
# 附录:文档注释
+编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,那么每次更改代码时更改文档都会变得很繁琐。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这完整的画面,您需要一个特殊的注释语法来标记文档,以及一个工具来将这些注释提取为有用的表单中。这就是Java所做的。
+
+提取注释的工具称为Javadoc,它是 JDK 安装的一部分。它使用Java编译器中的一些技术来寻找特殊的注释标记。它不仅提取由这些标记所标记的信息,还提取与注释相邻的类名或方法名。通过这种方式,您就可以用最少的工作量来生成合适的程序文档。
+
+Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于Javadoc,您有一个简单的标准来创建文档,因此您可以期望所有Java libraries都有文档。
+
+此外,您可以编写自己的Javadoc处理程序doclet,对于 Javadoc(例如,以不同的格式生成输出)。
+
+以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。
+
+
\ No newline at end of file
From 1963e693589da1bf0c6badb639d89674259e1603 Mon Sep 17 00:00:00 2001
From: Joe <736777445@qq.com>
Date: Sat, 21 Sep 2019 12:02:02 +0800
Subject: [PATCH 090/449] Update 12-Collections.md
---
docs/book/12-Collections.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md
index 407dcd96..61f47709 100644
--- a/docs/book/12-Collections.md
+++ b/docs/book/12-Collections.md
@@ -224,7 +224,7 @@ public class AddingGroups {
`Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。
-也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `delete()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
+也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `remove()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:
```java
// collections/AsListInference.java
From 81eaaaa0bcd6aa257b59e5fa4c0683cbb0d56501 Mon Sep 17 00:00:00 2001
From: withthewind
Date: Sat, 21 Sep 2019 12:58:42 +0800
Subject: [PATCH 091/449] Commit Appendix: Javadoc - Syntax
---
docs/book/Appendix-Javadoc.md | 36 +++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md
index cc25c5b9..02a5e059 100644
--- a/docs/book/Appendix-Javadoc.md
+++ b/docs/book/Appendix-Javadoc.md
@@ -13,6 +13,42 @@ Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于
以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。
+## 句法规则
+
+所有Javadoc指令都发生在以 **/**** 开头(但仍然以 ***/** 结尾)的注释中。
+
+使用Javadoc有两种主要方法:
+
+嵌入HTML或使用“doc标签”。独立的doc标签是指令它以 **@** 开头,放在注释行的开头。(然而,前面的 ***** 将被忽略。)可能会出现内联doc标签
+
+Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被花括号包围。
+
+有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子:
+
+```java
+
+// javadoc/Documentation1.java
+/** 一个类注释 */
+public class Documentation1 {
+ /** 一个属性注释 */
+ public int i;
+ /** 一个方法注释 */
+ public void f() {}
+}
+
+```
+
+Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。
+
+默认情况下,将忽略对 **私有成员** 和包访问成员的注释(请参阅["隐藏实现"](/docs/book/07-Implementation-Hiding.md)一章),并且您将看不到任何输出。
+
+这是有道理的,因为仅客户端程序员的观点是,在文件外部可以使用 **公共成员** 和 **受保护成员** 。 您可以使用 **-private** 标志和包含 **私人** 成员。
+
+要通过Javadoc处理前面的代码,命令是:
+
+**javadoc Documentation1.java**
+
+这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览您的类。
From dec81b2dfd1bdf8392d55f6521fc3371c130901b Mon Sep 17 00:00:00 2001
From: withthewind
Date: Sat, 21 Sep 2019 13:10:32 +0800
Subject: [PATCH 092/449] Update Fix Arrays Link Failure
---
docs/book/21-Arrays.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index 7e874a02..c8444fd1 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -4,7 +4,7 @@
# 第二十一章 数组
-> 在[初始化和清理](/book/06-Housekeeping.md)一章的最后,你已经学过如何定义和初始化一个数组。
+> 在[初始化和清理](/docs/book/06-Housekeeping.md)一章的最后,你已经学过如何定义和初始化一个数组。
简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 **集合** (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。
From 8284bc4d734ab4cfac97c71156f6cd78e0dba5d0 Mon Sep 17 00:00:00 2001
From: withthewind
Date: Sat, 21 Sep 2019 13:14:15 +0800
Subject: [PATCH 093/449] Insert Arrays - Collection Link
---
docs/book/21-Arrays.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index c8444fd1..955f84b8 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -19,7 +19,7 @@
将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据的能力。在Java中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。
-速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合]( ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个**ArrayList**的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。
+速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](/docs/book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个**ArrayList**的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。
数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 **RuntimeException** 的异常提醒,这表明你的程序中存在错误。
From 7ab6d7486412d297c09510517c38a53dd89e0ce7 Mon Sep 17 00:00:00 2001
From: withthewind
Date: Sat, 21 Sep 2019 13:16:14 +0800
Subject: [PATCH 094/449] Update Fix Arrays Link Failure
---
docs/book/21-Arrays.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index 955f84b8..a14ab4e7 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -91,7 +91,7 @@ Sphere 9
*/
```
-**Suppliers.create()** 方法在[泛型](/book/20-Generics.md)中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用[ ] 来随机存取元素,而一个List 使用诸如add()和get()等方法。数组和ArrayList之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](book/12-Collections.md)中看到的,集合的功能明显多于数组。随着Java自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。
+**Suppliers.create()** 方法在[泛型](/docs/book/20-Generics.md)中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用[ ] 来随机存取元素,而一个List 使用诸如add()和get()等方法。数组和ArrayList之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](/docs/book/12-Collections.md)中看到的,集合的功能明显多于数组。随着Java自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。
### 用于显示数组的实用程序
@@ -361,7 +361,7 @@ public class IceCreamFlavors {
**flaverset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后选择从数组 **FLAVORS** 中随机选择flavors并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何的对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者在其他的什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。
-如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/generics) 中介绍的 **元组** 。
+如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](/docs/book/20-Generics.md) 中介绍的 **元组** 。
注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串
From ab4879e32cc84e3ca13dbbaf09255f25390f02e5 Mon Sep 17 00:00:00 2001
From: cogitates
Date: Sun, 22 Sep 2019 18:07:30 +0800
Subject: [PATCH 095/449] =?UTF-8?q?Fix=20issue=20#117=20=E7=BF=BB=E8=AF=91?=
=?UTF-8?q?=E6=95=B0=E7=BB=84=E9=83=A8=E5=88=86=20=E7=AC=AC=E5=85=AB?=
=?UTF-8?q?=EF=BC=8C=E7=AC=AC=E4=B9=9D=EF=BC=8C=E7=AC=AC=E5=8D=81=E5=B0=8F?=
=?UTF-8?q?=E8=8A=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: cogitates
---
docs/book/21-Arrays.md | 1055 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 1055 insertions(+)
diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md
index 7e874a02..28f4021f 100644
--- a/docs/book/21-Arrays.md
+++ b/docs/book/21-Arrays.md
@@ -822,14 +822,1069 @@ public class SimpleSetAll {
## 增量生成
+这是一个方法库,用于为不同类型生成增量值。
+
+这些被作为内部类来生成容易记住的名字;比如,为了使用 **Integer** 工具你可以用 **new Conut.Interger()** , 如果你想要使用基本数据类型 **int** 工具,你可以用 **new Count.Pint()** (基本类型的名字不能被直接使用,所以它们都在前面添加一个 **P** 来表示基本数据类型'primitive', 我们的第一选择是使用基本类型名字后面跟着下划线,比如 **int_** 和 **double_** ,但是这种方式违背Java的命名习惯)。每个包装类的生成器都使用 **get()** 方法实现了它的 **Supplier** 。要使用**Array.setAll()** ,一个重载的 **get(int n)** 方法要接受(并忽略)其参数,以便接受 **setAll()** 传递的索引值。
+
+注意,通过使用包装类的名称作为内部类名,我们必须调用 **java.lang** 包来保证我们可以使用实际包装类的名字:
+
+```java
+// onjava/Count.java
+// Generate incremental values of different types
+package onjava;
+import java.util.*;
+import java.util.function.*;
+import static onjava.ConvertTo.*;
+
+public interface Count {
+ class Boolean
+ implements Supplier {
+ private boolean b = true;
+ @Override
+ public java.lang.Boolean get() {
+ b = !b;
+ return java.lang.Boolean.valueOf(b);
+ }
+ public java.lang.Boolean get(int n) {
+ return get();
+ }
+ public java.lang.Boolean[] array(int sz) {
+ java.lang.Boolean[] result =
+ new java.lang.Boolean[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pboolean {
+ private boolean b = true;
+ public boolean get() {
+ b = !b;
+ return b;
+ }
+ public boolean get(int n) { return get(); }
+ public boolean[] array(int sz) {
+ return primitive(new Boolean().array(sz));
+ }
+ }
+ class Byte
+ implements Supplier {
+ private byte b;
+ @Override
+ public java.lang.Byte get() { return b++; }
+ public java.lang.Byte get(int n) {
+ return get();
+ }
+ public java.lang.Byte[] array(int sz) {
+ java.lang.Byte[] result =
+ new java.lang.Byte[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pbyte {
+ private byte b;
+ public byte get() { return b++; }
+ public byte get(int n) { return get(); }
+ public byte[] array(int sz) {
+ return primitive(new Byte().array(sz));
+ }
+ }
+ char[] CHARS =
+ "abcdefghijklmnopqrstuvwxyz".toCharArray();
+ class Character
+ implements Supplier {
+ private int i;
+ @Override
+ public java.lang.Character get() {
+ i = (i + 1) % CHARS.length;
+ return CHARS[i];
+ }
+ public java.lang.Character get(int n) {
+ return get();
+ }
+ public java.lang.Character[] array(int sz) {
+ java.lang.Character[] result =
+ new java.lang.Character[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pchar {
+ private int i;
+ public char get() {
+ i = (i + 1) % CHARS.length;
+ return CHARS[i];
+ }
+ public char get(int n) { return get(); }
+ public char[] array(int sz) {
+ return primitive(new Character().array(sz));
+ }
+ }
+ class Short
+ implements Supplier {
+ short s;
+ @Override
+ public java.lang.Short get() { return s++; }
+ public java.lang.Short get(int n) {
+ return get();
+ }
+ public java.lang.Short[] array(int sz) {
+ java.lang.Short[] result =
+ new java.lang.Short[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pshort {
+ short s;
+ public short get() { return s++; }
+ public short get(int n) { return get(); }
+ public short[] array(int sz) {
+ return primitive(new Short().array(sz));
+ }
+ }
+ class Integer
+ implements Supplier {
+ int i;
+ @Override
+ public java.lang.Integer get() { return i++; }
+ public java.lang.Integer get(int n) {
+ return get();
+ }
+ public java.lang.Integer[] array(int sz) {
+ java.lang.Integer[] result =
+ new java.lang.Integer[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pint implements IntSupplier {
+ int i;
+ public int get() { return i++; }
+ public int get(int n) { return get(); }
+ @Override
+ public int getAsInt() { return get(); }
+ public int[] array(int sz) {
+ return primitive(new Integer().array(sz));
+ }
+ }
+ class Long
+ implements Supplier {
+ private long l;
+ @Override
+ public java.lang.Long get() { return l++; }
+ public java.lang.Long get(int n) {
+ return get();
+ }
+ public java.lang.Long[] array(int sz) {
+ java.lang.Long[] result =
+ new java.lang.Long[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Plong implements LongSupplier {
+ private long l;
+ public long get() { return l++; }
+ public long get(int n) { return get(); }
+ @Override
+ public long getAsLong() { return get(); }
+ public long[] array(int sz) {
+ return primitive(new Long().array(sz));
+ }
+ }
+ class Float
+ implements Supplier {
+ private int i;
+ @Override
+ public java.lang.Float get() {
+ return java.lang.Float.valueOf(i++);
+ }
+ public java.lang.Float get(int n) {
+ return get();
+ }
+ public java.lang.Float[] array(int sz) {
+ java.lang.Float[] result =
+ new java.lang.Float[sz];
+ Arrays.setAll(result, n -> get());
+ return result;
+ }
+ }
+ class Pfloat {
+ private int i;
+ public float get() { return i++; }
+ public float get(int n) { return get(); }
+ public float[] array(int sz) {
+ return primitive(new Float().array(sz));
+ }
+ }
+ class Double
+ implements Supplier