修改语句拗口问题 (#625)

* 1

* 2

* 没有第三种形式吧?

* 修改语句拗口问题

* 修改语句拗口问题 2

* 修改语句拗口问题 3

* 修改语句拗口问题 4

* 修改语句拗口问题 5

* 修改语句拗口问题 6

* 修改语句拗口问题 7

* 修改语句拗口问题 8

* 修改语句拗口问题 9

* 拗口问题

* 修改语句拗口问题 10

* 修改语句拗口问题 11
This commit is contained in:
andyphone 2020-12-01 12:55:39 +08:00 committed by GitHub
parent a9bda4acfd
commit 3708ab32f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 21 deletions

View File

@ -602,7 +602,9 @@ public interface Operations {
}
```
这是*模版*方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们:
这是*模板方法*设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模板方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们:
```java
// interface/Machine.java

View File

@ -8,7 +8,7 @@
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外部类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。
最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,"Why inner classes?"就应该使得内部类的益处明确显现了。
最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,就能明白使用内部类的好处了。
本章剩余部分包含了对内部类语法更加详尽的探索,这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始不需要。因此,本章最初的部分也许就是你现在所需的全部,你可以将更详尽的探索当作参考资料。
@ -170,7 +170,7 @@ public class Sequence {
0 1 2 3 4 5 6 7 8 9
```
**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。
**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** 字段。然而内部类可以访问其外部类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。
@ -325,7 +325,7 @@ public class TestParcel {
## 内部类方法和作用域
到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。
到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法重写了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。
这么做有两个理由:
@ -513,7 +513,7 @@ public class Parcel9 {
}
```
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 **final** 的(也就是说,它在初始化后不会改变,所以可以被当作 **final**,就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但是通常最好加上 **final** 作为一种暗示
如果在定义一个匿名内部类时,它要使用一个外部环境(在本匿名内部类之外定义)对象,那么编译器会要求其(该对象)参数引用是 **final** 或者是 “effectively final”也就是说该参数在初始化后不能被重新赋值所以可以当作 **final**)的,就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但通常加上 **final** 作为提醒比较好
如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:
@ -552,7 +552,7 @@ Inside instance initializer
In anonymous f()
```
在此例中,不要求变量一定是 **final** 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。
在此例中,不要求变量 **i** 一定是 **final** 的。因为 **i** 被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。
下例是带实例初始化的"parcel"形式。注意 `destination()` 的参数必须是 **final** 的,因为它们是在匿名类内部使用的(译者注:即使不加 **final**, Java 8 的编译器也会为我们自动加上 **final**,以保证数据的一致性)。
@ -591,15 +591,15 @@ Over budget!
在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 **if** 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。
匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
匿名内部类与正规的继承相比有些受限,因为匿名内部类要么继承类,要么实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。
<!-- Nested Classes -->
## 嵌套类
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着:
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为*嵌套类*。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着:
1. 创建嵌套类的对象,不需要其外部类的对象。
1. 创建嵌套类的对象,不需要其外部类的对象。
2. 不能从嵌套类的对象中访问非静态的外部类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:
@ -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()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。
@ -923,13 +923,13 @@ Other operation
在将要介绍的控制框架control framework可以看到更多使用内部类的具体例子。
应用程序框架application framework就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架通常是继承一个或多个类覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题。这是设计模式中模板方法的一个例子,模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。
应用程序框架application framework就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架通常是继承一个或多个类重写某些方法。你在重写的方法中写的代码定制了该应用程序框架提供的通用解决方案,来解决你的具体问题。这是设计模式中*模板方法*的一个例子,模板方法包含算法的基本结构,而且会调用一个或多个可重写的方法来完成该算法的运算。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可重写的方法就是变化的事物。
控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作*事件驱动*系统。应用程序设计中常见的问题之一是图形用户接口GUI它几乎完全是事件驱动的系统。
要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。接下来的问题就是,对于要控制什么,控制框架并不包含任何具体的信息。那些信息是在实现算法的 `action()` 部分时,通过继承来提供的。
要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪`ready()`”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。下面是一个控制框架,它不包含具体的控制信息。那些信息是通过继承(当算法的 `action()` 部分被实现时)来提供的。
首先,接口描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。下面的例子包含了某些实现:
这里是描述了所有控制事件的接口。之所以用抽象类代替了真正的接口,是因为默认行为都是根据时间来执行控制的。也因此包含了一些具体实现:
```java
// innerclasses/controller/Event.java
@ -955,7 +955,7 @@ public abstract class Event {
当希望运行 **Event** 并随后调用 `start()` 时,那么构造器就会捕获(从对象创建的时刻开始的)时间,此时间是这样得来的:`start()` 获取当前时间,然后加上一个延迟时间,这样生成触发事件的时间。`start()` 是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用 **Event** 对象。例如,如果想要重复一个事件,只需简单地在 `action()` 中调用 `start()` 方法。
`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中覆盖 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。
`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中重写 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。
下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\<**Event**\> 类型读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数foreach 语法用来连续获取 **List** 中的 **Event**`remove()` 方法用来从 **List** 中移除指定的 **Event**
@ -1150,7 +1150,7 @@ public class GreenhouseControls extends Controller {
一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。
下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是命令设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求:
下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是 *命令* 设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求:
```java
// innerclasses/GreenhouseController.java
@ -1248,9 +1248,9 @@ enclosingClassReference.super();
<!-- Can Inner Classes Be Overridden? -->
## 内部类可以被覆盖么?
## 内部类可以被重写么?
如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外部类的一个方法,其实并不起什么作用:
如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被重写吗?这看起来似乎是个很有用的思想,但是“重写”内部类就好像它是外部类的一个方法,其实并不起什么作用:
```java
// innerclasses/BigEgg.java
@ -1286,7 +1286,7 @@ New Egg()
Egg.Yolk()
```
默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。
默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“重写后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。
这个例子说明,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:
@ -1335,7 +1335,7 @@ BigEgg2.Yolk()
BigEgg2.Yolk.f()
```
现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且覆盖了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,覆盖后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。
现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且重写了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,重写后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。
<!-- Local Inner Classes -->
@ -1426,14 +1426,15 @@ Anonymous inner 9
```java
Counter.class
LocalInnerClass$1.class
LocalInnerClass$LocalCounter.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class
```
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与 **"$"** 的后面。
虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 Java 的标准命名方式所以产生的文件自动都是平台无关的。注意为了保证你的内部类能起作用Java 编译器会尽可能地转换它们。)
虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况[^1]。因为这是 Java 的标准命名方式所以产生的文件自动都是平台无关的。注意为了保证你的内部类能起作用Java 编译器会尽可能地转换它们。)
- **[1]** 另一方面,**$** 对Unix shell来说是一个元字符所以当你列出.class文件时有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是他们没有考虑这个问题而是认为你会很自然地关注源代码文件。
<!-- Summary -->
## 本章小结