diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index ca4abfc2..3265c2fc 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -6,7 +6,7 @@ > 一个定义在另一个类中的类,叫作内部类。 -内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。 +内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外部类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。 最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,"Why inner classes?"就应该使得内部类的益处明确显现了。 @@ -16,7 +16,7 @@ ## 创建内部类 -创建内部类的方式就如同你想的一样——把类的定义置于外围类的里面: +创建内部类的方式就如同你想的一样——把类的定义置于外部类的里面: ```java // innerclasses/Parcel1.java @@ -119,7 +119,7 @@ Tasmania ## 链接外部类 -到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。 +到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外部对象(enclosing object)之间就有了一种联系,所以它能访问其外部对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外部类的所有元素的访问权。 ```java // innerclasses/Sequence.java @@ -173,9 +173,9 @@ public class Sequence { **Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。 这里,**SequenceSelector** 是提供 **Selector** 功能的 **private** 类。可以看到,在 `main()` 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 `selector()` 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。 -最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外围类中的一个 **private** 字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。 +最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外部类中的一个 **private** 字段。然而内部类可以访问其外部类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。 -所以内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。 +所以内部类自动拥有对其外部类所有成员的访问权。这是如何做到的呢?当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外部类对象的引用。然后,在你访问此外部类的成员时,就是用那个引用来选择外部类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外部类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外部类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。 ## 使用 .this 和 .new @@ -597,10 +597,10 @@ Over budget! ## 嵌套类 -如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: +如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: -1. 要创建嵌套类的对象,并不需要其外围类的对象。 -2. 不能从嵌套类的对象中访问非静态的外围类对象。 +1. 要创建嵌套类的对象,并不需要其外部类的对象。 +2. 不能从嵌套类的对象中访问非静态的外部类对象。 嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西: @@ -644,11 +644,11 @@ public class Parcel11 { 在 `main()` 中,没有任何 **Parcel11** 的对象是必需的;而是使用选取 **static** 成员的普通语法来调用方法-这些方法返回对 **Contents** 和 **Destination** 的引用。 -就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外围类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。 +就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外部类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。 ### 接口内部的类 -嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样: +嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部接口,就像下面这样: ```java // innerclasses/ClassInInterface.java @@ -702,7 +702,7 @@ f() ### 从多层嵌套类中访问外部类的成员 -一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员,如下所示: +一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外部类的所有成员,如下所示: ```java // innerclasses/MultiNestingAccess.java @@ -738,11 +738,11 @@ public class MultiNestingAccess { 至此,我们已经看到了许多描述内部类的语法和语义,但是这并不能同答“为什么需要内部类”这个问题。那么,Java 设计者们为什么会如此费心地增加这项基本的语言特性呢? -一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。 +一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口。 -内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是: +内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外部类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外部类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是: -> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 +> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(译注:类或抽象类)。 @@ -811,17 +811,17 @@ public class MultiImplementation { 如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性: -1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。 -2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 +1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立。 +2. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 稍后就会展示一个这样的例子。 -3. 创建内部类对象的时刻并不依赖于外围类对象的创建 +3. 创建内部类对象的时刻并不依赖于外部类对象的创建 4. 内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体。 举个例子,如果 **Sequence.java** 不使用内部类,就必须声明"**Sequence** 是一个 **Selector**",对于某个特定的 **Sequence** 只能有一个 **Selector**,然而使用内部类很容易就能拥有另一个方法 `reverseSelector()`,用它来生成一个反方向遍历序列的 **Selector**,只有内部类才有这种灵活性。 ### 闭包与回调 -闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。 +闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。 在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁,你将会在 [函数式编程 ]() 这一章节中学习相关细节。尽管相对于内部类,你可能更喜欢使用 lambda 表达式实现闭包,但是你会看到并需要理解那些在 Java 8 之前通过内部类方式实现闭包的代码,因此仍然有必要来理解这种方式。 @@ -910,7 +910,7 @@ Other operation 3 ``` -这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。 +这个例子进一步展示了外部类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外部类的接口中添加东西,也没有修改外部类的接口。 注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。 内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。 @@ -989,7 +989,7 @@ public class Controller { 这正是内部类要做的事情,内部类允许: 1. 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的 `action()`。 -2. 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。 +2. 内部类能够很容易地访问外部类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。 考虑此控制框架的一个特定实现,如控制温室的运作:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类 **Event** 的多种派生版本。对于温室系统的每一种行为,都继承创建一个新的 **Event** 内部类,并在要实现的 `action()` 中编写控制代码。 @@ -1144,9 +1144,9 @@ public class GreenhouseControls extends Controller { } ``` -注意,**light**,**water** 和 **thermostat** 都属于外围类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。 +注意,**light**,**water** 和 **thermostat** 都属于外部类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。 -大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外围类 **GreenhouseContrlos** 的所有方法。 +大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外部类 **GreenhouseContrlos** 的所有方法。 一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。 @@ -1218,7 +1218,7 @@ Terminating ## 继承内部类 -因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: +因为内部类的构造器必须连接到指向其外部类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外部类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: ```java // innerclasses/InheritInner.java @@ -1238,7 +1238,7 @@ public class InheritInner extends WithInner.Inner { } ``` -可以看到,**InheritInner** 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法: +可以看到,**InheritInner** 只继承自内部类,而不是外部类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外部类对象的引用。此外,必须在构造器内使用如下语法: ```java enclosingClassReference.super(); @@ -1250,7 +1250,7 @@ enclosingClassReference.super(); ## 内部类可以被覆盖么? -如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外围类的一个方法,其实并不起什么作用: +如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外部类的一个方法,其实并不起什么作用: ```java // innerclasses/BigEgg.java @@ -1288,7 +1288,7 @@ Egg.Yolk() 默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。 -这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的: +这个例子说明,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的: ```java // innerclasses/BigEgg2.java @@ -1341,7 +1341,7 @@ BigEgg2.Yolk.f() ## 局部内部类 -前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。 +前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外部类的一部分;但是它可以访问当前代码块内的常量,以及此外部类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。 ```java // innerclasses/LocalInnerClass.java @@ -1421,7 +1421,7 @@ Anonymous inner 9 由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 **Class** 对象)。 -你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“**$**",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: +你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“**$**",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: ```java Counter.class @@ -1430,7 +1430,7 @@ LocalInnerClass$LocalCounter.class LocalInnerClass.class ``` -如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“**$**”的后面。 +如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与“**$**”的后面。 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index ed33d550..c22b1314 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1152,7 +1152,7 @@ public class Closure4 { 如果 `x` 和 `i` 的值在方法中的其他位置发生改变(但不在返回的函数内部),则编译器仍将视其为错误。每个递增操作则会分别产生错误消息。代码示例: ```java -/ functional/Closure5.java +// functional/Closure5.java // {无法编译成功} import java.util.function.*; diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index baa79c7b..e629542a 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -41,7 +41,7 @@ 这些定义很好,但是我们已有几十年混乱使用和抗拒解决此问题的历史了。一般来说,当人们使用“并发”这个词时,他们的意思是“所有的一切”。事实上,我自己也经常陷入这样的想法。在大多数书籍中,包括 *Brian Goetz* 的 《Java Concurrency in Practice》,都在标题中使用这个词。 “并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 - + 还有另一种方法,在减速发生的地方写下定义(原文Here’s another approach, writing the definitions around where the slowdown occurs): @@ -451,7 +451,7 @@ parallelPrefix: 265ms 第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。” -最后,考虑使用盒装**Long**的效果: +最后,考虑使用包装类**Long**的效果: ```java // concurrent/Summing3.java @@ -3111,7 +3111,7 @@ Pizza4: complete 并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且你必须确保多个任务不会同时读取和更改该资源。 -我花了多年的时间研究并发并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] +我花了多年的时间研究并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] 我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。