...
 
Commits (5)
    https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/8af837d424c99639a2a742ac37063436df1f1eeb 2021-09-20 20:13:12 2021-09-20T20:13:12+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/7a0721e451986eb9106e2ca84a61496e037e9990 2021-09-20 20:20:31 2021-09-20T20:20:31+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/25a56aee1b7fceb72287259f5b898f557699ec31 2021-09-20 20:46:22 2021-09-20T20:46:22+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/5f5cbe0cce81267dea88b45754e10dda73c98d94 2021-09-20 20:54:51 2021-09-20T20:54:51+08:00 wizardforcel 562826179@qq.com https://gitcode.net/OpenDocCN/apachecn-java-zh/-/commit/65bd59f2b1c6e5600450d1a4a9ce117608499ab9 2021-09-20 21:00:16 2021-09-20T21:00:16+08:00 wizardforcel 562826179@qq.com
......@@ -99,7 +99,7 @@ public class Car extends Vehicle
广义地说,多态为我们提供了一种选择,可以为不同类型的实体使用相同的接口。多态有两种主要类型:编译时和运行时。假设你有一个`Shape`类,它有两个区域方法。一个返回圆的面积,它接受一个整数;也就是说,输入半径,它返回面积。另一种方法计算矩形的面积,并采用两种输入:长度和宽度。编译器可以根据调用中参数的数量来决定调用哪个`area`方法。这是多态的编译时类型。
有一群技术人员认为只有运行时多态才是真正的多态。运行时多态,有时也称为子类型多态,在子类继承超类并重写其方法时起作用。在这种情况下,编译器无法决定最终是执行子类实现还是执行超类实现,因此在运行时做出决定。
有一群技术人员认为只有运行时多态才是真正的多态。运行时多态,有时也称为子类型多态,在子类继承超类并覆盖其方法时起作用。在这种情况下,编译器无法决定最终是执行子类实现还是执行超类实现,因此在运行时做出决定。
为了详细说明,让我们以前面的示例为例,向汽车类型添加一个新方法来打印对象的类型和名称:
......@@ -110,7 +110,7 @@ public String toString()
}
```
我们在派生的`Car`类中重写相同的方法:
我们在派生的`Car`类中覆盖相同的方法:
```java
public String toString()
......
......@@ -144,7 +144,7 @@ Vehicle vehicle = new Car();
Vehicle vehicle = new Truck();
```
但这里有两个问题。首先,我们的类应该为扩展而开放,为修改而关闭(开闭原则)。第二,每个班级应该只有一个改变的理由(单一责任原则)。每次添加一个新类时更改主代码将打破开放/关闭原则,让主类除了功能外还负责实例化`vehicle`对象将打破单一责任原则。
但这里有两个问题。首先,我们的类应该为扩展而开放,为修改而关闭(开闭原则)。第二,每个应该只有一个改变的理由(单一责任原则)。每次添加一个新类时更改主代码将打破开放/关闭原则,让主类除了功能外还负责实例化`vehicle`对象将打破单一责任原则。
在这种情况下,我们需要为代码提供更好的设计。我们可以添加一个新类来负责实例化`vehicle`对象。我们将基于这个`SimpleFactory`类调用模式。
......@@ -409,7 +409,7 @@ factory 方法有一个抽象方法,由每个具体的工厂用代码来实例
![](img/c3a042d9-7a7b-4839-b7e9-c2fb94e31516.png)
`CarBuilder`是构建器基类,它包含四个抽象方法。我们创建了两个混凝土建筑商`ElectricCarBuilder``GasolineCarBuilder`。每个具体的构建器都必须实现所有的抽象方法。不需要的方法,例如`ElectricCarBuilder``addGasTank`被保留为空,或者它们可以抛出异常。电动汽车和汽油汽车有不同的内部结构。
`CarBuilder`是构建器基类,它包含四个抽象方法。我们创建了两个混凝土构建器`ElectricCarBuilder``GasolineCarBuilder`。每个具体的构建器都必须实现所有的抽象方法。不需要的方法,例如`ElectricCarBuilder``addGasTank`被保留为空,或者它们可以抛出异常。电动汽车和汽油汽车有不同的内部结构。
`Director`类使用构建器来创建新的`Car`对象。`buildElectricCar``buildGasolineCar`可能相似,但略有不同:
......
......@@ -123,7 +123,7 @@ public void performAction(ActionEvent e)
}
```
但是,我们可以决定将命令模式应用于绘图应用。我们首先创建一个命令界面
但是,我们可以决定将命令模式应用于绘图应用。我们首先创建一个命令接口
```java
public interface Command
......@@ -132,7 +132,7 @@ public interface Command
}
```
下一步是将菜单项、按钮等所有对象定义为类,实现命令界面`execute()`方法:
下一步是将菜单项、按钮等所有对象定义为类,实现命令接口`execute()`方法:
```java
public class OpenMenuItem extends JMenuItem implements Command
......@@ -192,7 +192,7 @@ public class ClientThread
# 解释器模式
计算机应该用来解释句子或评估表达式。如果我们必须编写一系列代码来处理这样的需求,首先,我们需要知道结构;我们需要有表达式或句子的内部表示。在许多情况下,最适合使用的结构是基于复合模式的复合结构。我们将在第 4 章、“结构模式”中进一步讨论复合模式,目前我们可以将复合表示看作是将性质相似的对象分组在一起。
计算机应该用来解释句子或求值表达式。如果我们必须编写一系列代码来处理这样的需求,首先,我们需要知道结构;我们需要有表达式或句子的内部表示。在许多情况下,最适合使用的结构是基于复合模式的复合结构。我们将在第 4 章、“结构模式”中进一步讨论复合模式,目前我们可以将复合表示看作是将性质相似的对象分组在一起。
# 意图
......@@ -244,7 +244,7 @@ public class Number implements Expression
}
```
现在我们到了困难的部分。我们需要实现运营商。运算符是复合表达式,由两个表达式组成:
现在我们到了困难的部分。我们需要实现运算符。运算符是复合表达式,由两个表达式组成:
```java
public class Plus implements Expression
......@@ -504,7 +504,7 @@ System.out.println(item);
# 备忘录模式
封装是面向对象设计的基本原则之一。我们也知道每个班级都应该有一个单一的责任。当我们向对象添加功能时,我们可能会意识到我们需要保存其内部状态,以便能够在稍后的阶段恢复它。如果我们直接在类中实现这样的功能,那么类可能会变得太复杂,最终可能会打破单一责任原则。同时,封装阻止我们直接访问需要记忆的对象的内部状态。
封装是面向对象设计的基本原则之一。我们也知道每个都应该有一个单一的责任。当我们向对象添加功能时,我们可能会意识到我们需要保存其内部状态,以便能够在稍后的阶段恢复它。如果我们直接在类中实现这样的功能,那么类可能会变得太复杂,最终可能会打破单一责任原则。同时,封装阻止我们直接访问需要记忆的对象的内部状态。
# 意图
......@@ -658,7 +658,7 @@ public class CarCaretaker
让我们回到我们在讨论命令模式时介绍的形状应用。我们应用了命令模式,所以我们必须重做所实现的操作。是时候添加保存功能了。
我们可能会认为,如果我们向`Shape`基类添加一个抽象的`Save`方法,并对每个形状进行扩展,那么问题就解决了。这个解决方案也许是最直观的,但不是最好的。首先,每个班级应该有一个单一的职责。
我们可能会认为,如果我们向`Shape`基类添加一个抽象的`Save`方法,并对每个形状进行扩展,那么问题就解决了。这个解决方案也许是最直观的,但不是最好的。首先,每个应该有一个单一的职责。
其次,如果需要更改保存每个形状的格式,会发生什么情况?如果我们要实现相同的方法来生成 XML 输出,那么我们是否必须更改为 JSON 格式?这种设计绝对不遵循开/关原则。
......
......@@ -2,7 +2,7 @@
本章的目的是学习函数模式,以及通过引入函数式编程风格(现在在最重要的编程语言中是可能的)对传统模式所做的更改。Java8 引入了一些功能特性,增加了一个新的抽象级别,影响了我们编写一些面向对象设计模式的方式,甚至使其中一些模式变得无关紧要。在本章中,我们将看到设计模式是如何被新的语言特性所改变,甚至取代的。在他的论文《动态语言中的设计模式》中,Peter Norvig 注意到 23 种设计模式中有 16 种更简单,或者被动态语言中现有的语言特征所取代,比如 Dylan。全文见[这个页面](http://norvig.com/design-patterns/)。在这一章中,我们将看到什么可以被取代,以及新出现的模式是怎样和怎样的。正如 peternorvig 在他的论文中所说的,*很久以前,子程序调用只是一种模式*,随着语言的发展,这些模式会发生变化或被替换。
为了运行本章中的代码,我们使用了 Java 中可用的 JShell REPL 实用程序,可以从 Windows 中的`$JAVA_HOME/bin/jshell on Linux or %JAVA_HOME%/bin/jshell.exe`访问该实用程序
为了运行本章中的代码,我们使用了 Java 中可用的 JShell REPL 工具,可以从 Windows 中的`$JAVA_HOME/bin/jshell on Linux or %JAVA_HOME%/bin/jshell.exe`访问该工具
# 函数式编程简介
......@@ -151,7 +151,7 @@ $40 ==> 110
# 函子
函子允许我们对给定的容器应用函数。他们知道如何从包装对象中展开值,应用给定的函数,并返回另一个包含结果/转换包装对象的函子。它们很有用,因为它们抽象了多种习惯用法,如集合、未来(承诺)和选择。下面的代码演示了 Java 中的`Optional`函子的用法,其中`Optional`可以是一个给定的值,这是将函数应用于现有的包装值(`5``Optional`的结果):
函子允许我们对给定的容器应用函数。他们知道如何从包装对象中展开值,应用给定的函数,并返回另一个包含结果/转换包装对象的函子。它们很有用,因为它们抽象了多种习惯用法,如集合、`Future``Promise`)和`Optional`。下面的代码演示了 Java 中的`Optional`函子的用法,其中`Optional`可以是一个给定的值,这是将函数应用于现有的包装值(`5``Optional`的结果):
```java
jshell> Optional<Integer> a = Optional.of(5);
......@@ -263,7 +263,7 @@ jshell> isLower.test("Lower")
$9 ==> false
```
* `Supplier<T>`:这是一个价值供应商
* `Supplier<T>`:这是一个值供应器
```java
jshell> String lambda = "Hello Lambda"
......@@ -407,7 +407,7 @@ Something is Done.
# 构建器
Lombock 库将生成器作为其功能的一部分引入。只要使用`@Builder`,任何类都可以自动获得对`builder`方法的访问权,如 Lombock 示例代码在[这个页面](https://projectlombok.org/features/Builder)中所示:
Lombock 库将生成器作为其功能的一部分引入。只要使用`@Builder`,任何类都可以自动获得对`builder`方法的访问权,如 Lombock 示例代码在[这个页面](https://projectlombok.org/features/Builder)中所示:
```java
Person.builder().name("Adam Savage").city("San Francisco").job("Mythbusters").job("Unchained Reaction").build();
......@@ -664,7 +664,7 @@ Execute statement...
# 尾部调用优化
**尾部调用优化****TCO**)是一些编译器在不使用栈空间的情况下调用函数的技术。Scala 通过用`@tailrec`递归代码来利用它。这基本上告诉编译器使用一个特殊的循环,称为 trampoline,它反复运行函数。函数调用可以处于一种或多种要调用的状态。在完成时,它返回结果(头部),在更多的情况下,它返回当前循环而不返回头部(尾部)。这个模式已经被cyclops-react提供给我们了。
**尾部调用优化****TCO**)是一些编译器在不使用栈空间的情况下调用函数的技术。Scala 通过用`@tailrec`递归代码来利用它。这基本上告诉编译器使用一个特殊的循环,称为 trampoline,它反复运行函数。函数调用可以处于一种或多种要调用的状态。在完成时,它返回结果(头部),在更多的情况下,它返回当前循环而不返回头部(尾部)。这个模式已经被cyclops-react提供给我们了。
# 意图
......
......@@ -40,11 +40,11 @@
反应式编程使用以下一些编程抽象,有些抽象取自函数式编程世界:
* **未来/承诺**:这些提供了一种手段,可以对不久的将来某个地方将要提供的价值采取行动。
* **`Optional`/`Promise`**:这些提供了一种手段,可以对不久的将来某个地方将要提供的值采取行动。
* **流**:它提供了数据管道,就像列车轨道一样,为列车运行提供了基础设施。
* **数据流变量**:这些是应用于流函数的输入变量的函数的结果,就像电子表格单元格一样,通过对两个给定的输入参数应用加号数学函数来设置。
* **节流**:该机制用于实时处理环境,包括**数字信号处理器****DSP**)等硬件,通过丢弃元件来调节输入处理的速度,以赶上输入速度;用作背压策略。
* **推送机制**:这与好莱坞原理相似,因为它反转了呼叫方向。一旦数据可用,就调用流中的相关观察者来处理数据;相反,拉机制以同步方式获取信息。
* **推送机制**:这与好莱坞原理相似,因为它反转了调用方向。一旦数据可用,就调用流中的相关观察者来处理数据;相反,拉机制以同步方式获取信息。
有许多 Java 库和框架允许程序员编写反应式代码,如 Reactor、Ratpack、RxJava、Spring Framework 5 和 Vert.x。通过添加 JDK9 Flow API,开发人员可以使用反应式编程,而无需安装其他 API。
......@@ -186,11 +186,11 @@ RxJava `Observable`应用于小数据集(最长不超过 1000 个元素),
# `from`运算符
通过调用以下方法之一,可以从数组、未来或其他对象和数据结构进行转换:
通过调用以下方法之一,可以从数组、`Future`或其他对象和数据结构进行转换:
* `fromArray`:将数组转换为可观察数组
* `fromCallable`:将提供值的`Callable`转换为`Observable`
* `fromFuture`:将未来提供的价值转换为可观察的价
* `fromFuture`:将`Future`提供的值转换为可观察的
* `fromIterable`:将`Iterable`转换为`Observable`
* `fromPublisher`:将反应发布者流转换为可观察发布者流
* `just`:将给定对象转换为可观察对象
......
......@@ -410,7 +410,7 @@ public Employee EmployeeDataService(@PathVariable("id") String id) throws Servle
服务 X 将根据服务 Y 执行的操作的重要性来优雅地处理此故障。例如,如果服务 Y 负责更新帐户详细信息,服务 X 将向调用服务报告故障,或者对于 Y 正在执行的记录事务详细信息的所有服务,服务 X 将添加日志详细信息到回退队列,当服务 Y 备份时,它可以被清除。
这里的重要因素是不要让一次服务故障导致整个系统瘫痪。呼叫服务应该找出哪些是不健康的服务,并管理备用方法。
这里的重要因素是不要让一次服务故障导致整个系统瘫痪。调用服务应该找出哪些是不健康的服务,并管理备用方法。
# 故障处理模式
......
......@@ -43,7 +43,7 @@
**数据访问层**负责管理所有与数据相关的操作,如获取数据、以所需格式表示数据、清理数据、存储数据、更新数据等。在创建这个层时,我们可以使用一个**对象关系映射****ORM**)框架或者创建我们自己的处理器。这里的想法是让其他层不必担心数据处理,也就是数据的存储方式。它是来自另一个第三方服务还是存储在本地?这些和类似的问题仅由该层负责。
**横切关注点**是每一层需要处理的关注点,例如,每一层负责检查请求是否来自正确的通道,没有未经授权的请求得到服务。每个层可能希望通过记录每条消息来记录请求的进入和退出。这些问题可以通过跨层使用和分布的公共实用程序来处理,也可以由每个层独立处理。通常,使用诸如**面向切面编程****AOP**)之类的技术,使这些关注点独立于核心业务或应用逻辑是一个好主意。
**横切关注点**是每一层需要处理的关注点,例如,每一层负责检查请求是否来自正确的通道,没有未经授权的请求得到服务。每个层可能希望通过记录每条消息来记录请求的进入和退出。这些问题可以通过跨层使用和分布的公共工具来处理,也可以由每个层独立处理。通常,使用诸如**面向切面编程****AOP**)之类的技术,使这些关注点独立于核心业务或应用逻辑是一个好主意。
# 分层架构及其应用实例
......@@ -181,7 +181,7 @@ public List<Employee> getEmployeeList()
* `3`:控制器根据给定的请求操作或更新模型,并返回最终用户请求的模型
* `4`:然后框架选择要处理当前请求的视图,并将模型传递给它
* `5`:视图通常是 JSP,根据提供的模型呈现数据
* `6`:最后的响应通常是 HTML,发送回呼叫代理或浏览器
* `6`:最后的响应通常是 HTML,发送回调用代理或浏览器
# MVC 架构及其应用实例
......@@ -488,7 +488,7 @@ REST 没有那么多规则和格式。REST 服务可以通过 HTTP 支持`GET`
* **依赖 devops**:由于我们需要维护多个通过消息相互作用的服务,因此我们需要确保所有服务都可用并受到适当的监控。
* **保持平衡**:维持适量的微服务本身就是一个挑战。如果我们有太细粒度的服务,我们就会面临部署和维护太多服务等挑战。另一方面,如果我们拥有的大型服务太少,我们最终会失去微服务所提供的优势。
* **重复代码**:由于我们所有的服务都是独立开发和部署的,一些常用的实用程序需要复制到不同的服务中。
* **重复代码**:由于我们所有的服务都是独立开发和部署的,一些常用的工具需要复制到不同的服务中。
# 无服务器架构
......@@ -630,6 +630,6 @@ public class LambdaMethodHandler implements RequestStreamHandler
在本章中,我们讨论了各种架构风格,从分层架构、MVC 架构、面向服务架构、微服务开始,最后是无服务器架构。我想到的一个明显的问题是:在这些设计应用的风格中,哪一种是最好的。这个问题的答案也很明显,这取决于手头的问题。好吧,如果有一个架构可以应用于所有的问题,每个人都会使用它,我们只会讨论那个特定的架构风格。
这里需要注意的一点是,这些建筑风格并不是相互排斥的;事实上,它们是相辅相成的。因此,大多数时候,您可能会使用这些建筑风格的混合体。例如,如果我们正在进行基于面向服务架构的设计,我们可能会看到这些服务的内部实现可能是基于分层或 MVC 架构的。此外,我们可能最终将一些服务分解为微服务,而在这些微服务中,一些可能以无服务器的方式实现为函数。关键是,您必须根据当前要解决的问题选择设计或架构。
这里需要注意的一点是,这些架构风格并不是相互排斥的;事实上,它们是相辅相成的。因此,大多数时候,您可能会使用这些架构风格的混合体。例如,如果我们正在进行基于面向服务架构的设计,我们可能会看到这些服务的内部实现可能是基于分层或 MVC 架构的。此外,我们可能最终将一些服务分解为微服务,而在这些微服务中,一些可能以无服务器的方式实现为函数。关键是,您必须根据当前要解决的问题选择设计或架构。
在下一章中,我们将重点介绍最近 Java 版本升级中的一些最新趋势和更新。
\ No newline at end of file
......@@ -567,7 +567,7 @@ public static String joinByDelimiter(char delimiter, String...args) {
}
```
从 Java8 开始,这个问题至少还有三种解决方案。其中一个解决方案依赖于`StringJoiner`实用程序类。此类可用于构造由分隔符(例如逗号)分隔的字符序列。
从 Java8 开始,这个问题至少还有三种解决方案。其中一个解决方案依赖于`StringJoiner`工具类。此类可用于构造由分隔符(例如逗号)分隔的字符序列。
它还支持可选的前缀和后缀(此处忽略):
......@@ -918,7 +918,7 @@ public static String removeCharacter(String str, String ch) {
2. 其次,计算`HashMap`中表示最大出现次数的最大值(例如,使用`Collections.max()`
3. 最后,通过循环`HashMap`条目集,得到出现次数最多的字符
工具方法返回`Pair<Character, Integer>`,其中包含出现次数最多的字符和出现次数(注意,忽略了空格)。如果你不喜欢这个额外的课程,也就是说,`Pair`,那就依赖`Map.Entry<K, V>`
工具方法返回`Pair<Character, Integer>`,其中包含出现次数最多的字符和出现次数(注意,忽略了空格)。如果你不喜欢这个额外的,也就是说,`Pair`,那就依赖`Map.Entry<K, V>`
```java
public static Pair<Character, Integer> maxOccurenceCharacter(
......@@ -1536,7 +1536,7 @@ int min = Math.min(i1, i2);
int max = Math.max(i1, i2);
```
`Math`类为每个基元数字类型(`int``long``float``double`提供了`min()``max()`方法。
`Math`类为每个原始数字类型(`int``long``float``double`提供了`min()``max()`方法。
从 JDK8 开始,每个原始数字类型的包装器类(`Integer``Long``Float``Double`都有专用的`min()``max()`方法,在这些方法后面,还有来自`Math`类的对应调用。请参见下面的示例(这是一个更具表现力的示例):
......@@ -1906,7 +1906,7 @@ int z = 222/14;
这一次,`z`将等于 15,这正是预期的结果(`/`运算符返回最接近零的整数)。无需申请`Math.floor(z)`。此外,如果除数为 0,则`222/0`将抛出`ArithmeticException`
到目前为止的结论是,两个符号相同的整数(都是正的或负的)的除法底可以通过`/`算子得到。
到目前为止的结论是,两个符号相同的整数(都是正的或负的)的除法底可以通过`/`运算符得到。
好的,到目前为止,很好,但是假设我们有以下两个整数(相反的符号;被除数是负数,除数是正数,反之亦然):
......@@ -2117,7 +2117,7 @@ public static NumberFormat getCompactNumberInstance​(
默认情况下,使用`RoundingMode.HALF_EVEN`格式化数字。但是,我们可以通过`NumberFormat.setRoundingMode()`显式设置舍入模式。
尝试将这些信息压缩成一个名为`NumberFormatters`实用程序类可以实现如下:
尝试将这些信息压缩成一个名为`NumberFormatters`工具类可以实现如下:
```java
public static String forLocale(Locale locale, double number) {
......
# 对象、不变性和`switch`表达式
本章包括 18 个涉及对象、不变性和`switch`表达式的问题。本章从处理`null`参考文献的几个问题入手。它继续处理有关检查索引、`equals()``hashCode()`以及不变性(例如,编写不可变类和从不可变类传递/返回可变对象)的问题。本章的最后一部分讨论了克隆对象和 JDK12`switch`表达式。本章结束时,您将掌握对象和不变性的基本知识。此外,你将知道如何处理新的`switch`表达式。在任何 Java 开发人员的武库中,这些都是有价值的、非可选的知识。
本章包括 18 个涉及对象、不变性和`switch`表达式的问题。本章从处理`null`引用的几个问题入手。它继续处理有关检查索引、`equals()``hashCode()`以及不变性(例如,编写不可变类和从不可变类传递/返回可变对象)的问题。本章的最后一部分讨论了克隆对象和 JDK12`switch`表达式。本章结束时,您将掌握对象和不变性的基本知识。此外,你将知道如何处理新的`switch`表达式。在任何 Java 开发人员的武库中,这些都是有价值的、非可选的知识。
# 问题
......@@ -314,7 +314,7 @@ public Car(String name, Color color) {
}
```
此外,我们也可以通过这些例子来丰富`MyObjects`中的其他例外
此外,我们也可以通过这些例子来丰富`MyObjects`中的其他异常
# 43 检查空引用并返回非空默认引用
......@@ -559,7 +559,7 @@ int r = f.yMinusX(30, 20);
`equals()``hashCode()`方法在`java.lang.Object`中定义。因为`Object`是所有 Java 对象的超类,所以这两种方法对所有对象都可用。他们的主要目标是为比较对象提供一个简单、高效、健壮的解决方案,并确定它们是否相等。如果没有这些方法和它们的契约,解决方案依赖于庞大而繁琐的`if`语句来比较对象的每个字段。
当这些方法没有被重写时,Java 将使用它们的默认实现。不幸的是,默认实现并不能真正实现确定两个对象是否具有相同值的目标。默认情况下,`equals()`检查*相等性*。换言之,当且仅当两个对象由相同的内存地址(相同的对象引用)表示时,它认为这两个对象相等,而`hashCode()`返回对象内存地址的整数表示。这是一个本机函数,称为*标识**哈希码。*
当这些方法没有被覆盖时,Java 将使用它们的默认实现。不幸的是,默认实现并不能真正实现确定两个对象是否具有相同值的目标。默认情况下,`equals()`检查*相等性*。换言之,当且仅当两个对象由相同的内存地址(相同的对象引用)表示时,它认为这两个对象相等,而`hashCode()`返回对象内存地址的整数表示。这是一个本机函数,称为*标识**哈希码。*
例如,假设以下类:
......@@ -592,7 +592,7 @@ System.out.println("p2 hash code: " + p2.hashCode()); // 157627094
根据经验,如果两个变量拥有相同的引用,则它们*相同*,但是如果它们引用相同的值,则它们*相等**相同值*的含义由`equals()`定义。
对我们来说,`p1``p2`是相等的,但是请注意`equals()`返回了`false``p1``p2`实例的字段值完全相同,但是它们存储在不同的内存地址)。这意味着依赖于`equals()`的默认实现是不可接受的。解决方法是重写此方法,为此,重要的是要了解`equals()`合同,该合同规定了以下声明:
对我们来说,`p1``p2`是相等的,但是请注意`equals()`返回了`false``p1``p2`实例的字段值完全相同,但是它们存储在不同的内存地址)。这意味着依赖于`equals()`的默认实现是不可接受的。解决方法是覆盖此方法,为此,重要的是要了解`equals()`合同,该合同规定了以下声明:
* **自反性**:对象等于自身,即`p1.equals(p1)`必须返回`true`
* **对称性**`p1.equals(p2)`必须返回与`p2.equals(p1)`相同的结果(`true`/`false`)。
......@@ -600,7 +600,7 @@ System.out.println("p2 hash code: " + p2.hashCode()); // 157627094
* **一致性**:两个相等的物体必须一直保持相等,除非其中一个改变。
* **`null`返回`false`**:所有对象必须不等于`null`
因此,为了遵守此约定,`Player`类的`equals()`方法可以重写如下:
因此,为了遵守此约定,`Player`类的`equals()`方法可以覆盖如下:
```java
@Override
......@@ -669,9 +669,9 @@ System.out.println("Set contains Rafael Nadal: "
* 当创建`p2`时,Java 将根据`p2`内存地址为其分配一个哈希码。
*`p2`被添加到`Set`时,Java 会将一个新的桶链接到`p2`哈希码(当这种情况发生时,看起来`HashSet`没有按预期工作,它允许重复)。
* 当执行`players.contains(new Player(1, "Rafael Nadal"))`时,基于`p3`存储器地址用新的哈希码创建新的播放器`p3`
* 因此,在`contains()`的框架中,分别测试`p1``p3 p2``p3`的相等性涉及检查它们的哈希码,由于`p1`哈希码不同于`p3`哈希码,而`p2`哈希码不同于`p3`哈希码,比较停止,没有评估`equals()`,这意味着`HashSet`不包含对象(`p3`
* 因此,在`contains()`的框架中,分别测试`p1``p3 p2``p3`的相等性涉及检查它们的哈希码,由于`p1`哈希码不同于`p3`哈希码,而`p2`哈希码不同于`p3`哈希码,比较停止,没有求值`equals()`,这意味着`HashSet`不包含对象(`p3`
为了回到正轨,代码也必须重写`hashCode()`方法。`hashCode()`合同规定如下:
为了回到正轨,代码也必须覆盖`hashCode()`方法。`hashCode()`合同规定如下:
* 符合`equals()`的两个相等对象必须返回相同的哈希码。
* 具有相同哈希码的两个对象不是强制相等的。
......@@ -679,10 +679,10 @@ System.out.println("Set contains Rafael Nadal: "
根据经验,为了尊重`equals()``hashCode()`合同,遵循两条黄金法则:
*`equals()`重写时,`hashCode()`也必须被重写,反之亦然。
*`equals()`覆盖时,`hashCode()`也必须被覆盖,反之亦然。
* 以相同的顺序对两个方法使用相同的标识属性。
对于`Player`类,`hashCode()`可以被重写如下:
对于`Player`类,`hashCode()`可以被覆盖如下:
```java
@Override
......@@ -716,7 +716,7 @@ System.out.println("Set contains Rafael Nadal: "
* 确保实例是我们期望的(使用`getClass()``instanceof`
* 最后,在这些角落案例之后,添加字段比较。
* 你通过继承来破坏对称。假设一个类`A`和一个类`B`扩展了`A`并添加了一个新字段。`B`重写`A`继承的`equals()`实现,并将此实现添加到新字段中。依赖`instanceof`会发现`b.equals(a)`会返回`false`(如预期),而`a.equals(b)`会返回`true`(非预期),因此对称性被破坏。依赖*切片比较*是行不通的,因为这会破坏及物性和自反性。解决这个问题意味着依赖于`getClass()`而不是`instanceof`(通过`getClass()`,类型及其子类型的实例不能相等),或者更好地依赖于组合而不是继承,就像绑定到本书中的应用(`P46_ViolateEqualsViaSymmetry`一样)。
* 你通过继承来破坏对称。假设一个类`A`和一个类`B`扩展了`A`并添加了一个新字段。`B`覆盖`A`继承的`equals()`实现,并将此实现添加到新字段中。依赖`instanceof`会发现`b.equals(a)`会返回`false`(如预期),而`a.equals(b)`会返回`true`(非预期),因此对称性被破坏。依赖*切片比较*是行不通的,因为这会破坏及物性和自反性。解决这个问题意味着依赖于`getClass()`而不是`instanceof`(通过`getClass()`,类型及其子类型的实例不能相等),或者更好地依赖于组合而不是继承,就像绑定到本书中的应用(`P46_ViolateEqualsViaSymmetry`一样)。
* 返回一个来自`hashCode()`的常量,而不是每个对象的唯一哈希码。
自 JDK7 以来,`Objects`类提供了几个帮助程序来处理对象相等和哈希码,如下所示:
......@@ -814,7 +814,7 @@ String z = "book";
应该声明一个不可变的类`final`,以避免扩展性。然而,开发人员需要扩展`String`类以添加更多的特性,这一限制可以被认为是不变性的一个缺点。
然而,开发人员可以编写实用程序类(例如,Apache Commons Lang、`StringUtils`、Spring 框架、`StringUtils`、Guava 和字符串)来提供额外的特性,并将字符串作为参数传递给这些类的方法。
然而,开发人员可以编写工具类(例如,Apache Commons Lang、`StringUtils`、Spring 框架、`StringUtils`、Guava 和字符串)来提供额外的特性,并将字符串作为参数传递给这些类的方法。
# 敏感数据长时间存储在内存中
......@@ -855,7 +855,7 @@ System.out.println("User is of type: " + user);
一个不可变的类必须满足几个要求,例如:
* 该类应标记为`final`以抑制可扩展性(其他类不能扩展该类;因此,它们不能重写方法)
* 该类应标记为`final`以抑制可扩展性(其他类不能扩展该类;因此,它们不能覆盖方法)
* 所有字段都应该声明为`private``final`(在其他类中不可见,在这个类的构造器中只初始化一次)
* 类应该包含一个参数化的`public`构造器(或者一个`private`构造器和用于创建实例的工厂方法),用于初始化字段
* 类应该为字段提供获取器
......@@ -1025,7 +1025,7 @@ public Radius getRadius() {
# 51 通过生成器模式编写不可变类
当一个类(不可变或可变)有太多字段时,它需要一个具有许多参数的构造器。当其中一些字段是必需的,而其他字段是可选的时,这个类将需要几个构造器来覆盖所有可能的组合。这对于开发人员和类的用户来说都是很麻烦的。这就是建筑商模式的用武之地。
当一个类(不可变或可变)有太多字段时,它需要一个具有许多参数的构造器。当其中一些字段是必需的,而其他字段是可选的时,这个类将需要几个构造器来覆盖所有可能的组合。这对于开发人员和类的用户来说都是很麻烦的。这就是构建器模式的用武之地。
根据**四人帮****GoF**),*构建器模式将复杂对象的构造与其表示分离,以便相同的构造过程可以创建不同的表示*
......@@ -1264,7 +1264,7 @@ Point clone = point.clonePoint();
* 覆盖`clone()`方法(`Object.clone()``protected`)。
* 调用`super.clone()`
`Cloneable`接口不包含任何方法。这只是 JVM 可以克隆这个对象的一个信号。一旦实现了这个接口,代码就需要重写`Object.clone()`方法。这是需要的,因为`Object.clone()``protected`,为了通过`super`调用它,代码需要重写这个方法。如果将`clone()`添加到子类中,这可能是一个严重的缺点,因为所有超类都应该定义一个`clone()`方法,以避免`super.clone()`链调用失败。
`Cloneable`接口不包含任何方法。这只是 JVM 可以克隆这个对象的一个信号。一旦实现了这个接口,代码就需要覆盖`Object.clone()`方法。这是需要的,因为`Object.clone()``protected`,为了通过`super`调用它,代码需要覆盖这个方法。如果将`clone()`添加到子类中,这可能是一个严重的缺点,因为所有超类都应该定义一个`clone()`方法,以避免`super.clone()`链调用失败。
此外,`Object.clone()`不依赖构造器调用,因此开发人员无法控制对象构造:
......@@ -1436,7 +1436,7 @@ Point clone = cloneThroughJson(point);
除此之外,您还可以选择编写专用于克隆对象的库。
# 54 重写`toString()`
# 54 覆盖`toString()`
`toString()`方法在`java.lang.Object`中定义,JDK 附带了它的默认实现。此默认实现自动用于`print()``println()``printf()`、开发期间调试、日志记录、异常中的信息消息等的所有对象。
......@@ -1468,7 +1468,7 @@ System.out.println(user);
![](img/d41b9942-344f-444f-b3a4-dbd2513c92cf.png)
在前面的屏幕截图中,避免输出的解决方案包括重写`toString()`方法。例如,让我们重写它以公开用户详细信息,如下所示:
在前面的屏幕截图中,避免输出的解决方案包括覆盖`toString()`方法。例如,让我们覆盖它以公开用户详细信息,如下所示:
```java
@Override
......
......@@ -1190,7 +1190,7 @@ static TemporalAdjuster NEXT_SATURDAY
LocalDate nextSaturday = date.with(NEXT_SATURDAY);
```
最后,这个函数式接口定义了一个名为`adjustInto()``abstract`方法。在自定义实现中,可以通过向该方法传递一个`Temporal`对象来重写该方法,如下所示:
最后,这个函数式接口定义了一个名为`adjustInto()``abstract`方法。在自定义实现中,可以通过向该方法传递一个`Temporal`对象来覆盖该方法,如下所示:
```java
public class NextSaturdayAdjuster implements TemporalAdjuster {
......@@ -1323,7 +1323,7 @@ Date dateFromInstant = Date.from(instant);
请记住,`Date`不是时区感知的,但它显示在系统默认时区中(例如,通过`toString()`)。`Instant`是 UTC 时区。
让我们快速地将这些代码片段包装在两个实用程序方法中,它们在一个实用程序`DateConverters`中定义:
让我们快速地将这些代码片段包装在两个工具方法中,它们在一个工具`DateConverters`中定义:
```java
public static Instant dateToInstant(Date date) {
......@@ -1496,7 +1496,7 @@ public static Date offsetTimeToDate(OffsetTime offsetTime) {
# JDK8 之前
在 JDK8 之前,解决方案可以依赖于`Calendar`实用程序类。绑定到本书的代码包含此解决方案。
在 JDK8 之前,解决方案可以依赖于`Calendar`工具类。绑定到本书的代码包含此解决方案。
# 从 JDK8 开始
......@@ -1852,7 +1852,7 @@ System.out.println(tickClock.instant());
1. 扩展`Clock`类。
2. 执行`Serializable`
3. 至少重写`Clock`继承的抽象方法。
3. 至少覆盖`Clock`继承的抽象方法。
`Clock`类的框架如下:
......
......@@ -579,7 +579,7 @@ stack.forEach(...);
现在,当我们从`Stack`切换到`ArrayQueue`时,我们应该更快地注意到错误并能够修复它。
# 88 LVTI 与三元算子
# 88 LVTI 与三元运算符
只要写入正确,*三元*运算符允许我们在右侧使用不同类型的操作数。例如,以下代码将不会编译:
......
......@@ -227,7 +227,7 @@ public static <T> void bubbleSortWithComparator(
}
```
还记得以前的吗?好吧,我们可以通过实现`Comparator`接口为它写一个`Comparator`
还记得以前的吗?好吧,我们可以通过实现`Comparator`接口为它写一个`Comparator`
```java
public class MelonComparator implements Comparator<Melon> {
......@@ -250,7 +250,7 @@ Comparator<Melon> byType
= Comparator.comparing(Melon::getType).reversed();
```
在一个名为`ArraySorts`实用程序类中,有一个`Melon`数组、前面的`Comparator`数组和`bubbleSortWithComparator()`方法,我们可以按照下面的思路编写一些东西:
在一个名为`ArraySorts`工具类中,有一个`Melon`数组、前面的`Comparator`数组和`bubbleSortWithComparator()`方法,我们可以按照下面的思路编写一些东西:
```java
Melon[] melons = {...};
......@@ -610,7 +610,7 @@ public static <T> boolean
同样,我们可以依赖于`Arrays.asList(arr).contains(find)`。基本上,将数组转换为一个`List`并调用`contains()`方法。在幕后,这种方法使用的是`equals()`合同。
如果此方法存在于名为`ArraySearch`实用程序类中,则以下调用将返回`true`
如果此方法存在于名为`ArraySearch`工具类中,则以下调用将返回`true`
```java
// true
......@@ -754,7 +754,7 @@ public static <T> int findIndexOfElementObject(
# 101 检查两个数组是否相等或不匹配
如果两个基元数组包含相同数量的元素,则它们相等,并且两个数组中所有对应的元素对都相等
如果两个原始数组包含相同数量的元素,则它们相等,并且两个数组中所有对应的元素对都相等
这两个问题的解决依赖于`Arrays`实用类。下面几节给出了解决这些问题的方法。
......@@ -931,7 +931,7 @@ int i13 = Arrays.compare(integers1, integers3); // 1
int is13 = Arrays.compare(integers1, 3, 6, integers3, 3, 6); // 1
```
对于`Object`的数组,`Arrays`类还提供了一组专用的`compare()`方法。还记得`Melon`吗?好吧,为了比较两个没有显式`Comparator``Melon`数组,我们需要实现`Comparable`接口和`compareTo()`方法。假设我们依赖于瓜的重量,如下所示:
对于`Object`的数组,`Arrays`类还提供了一组专用的`compare()`方法。还记得`Melon`吗?好吧,为了比较两个没有显式`Comparator``Melon`数组,我们需要实现`Comparable`接口和`compareTo()`方法。假设我们依赖于瓜的重量,如下所示:
```java
public class Melon implements Comparable {
......@@ -1039,7 +1039,7 @@ Stream<String> stream = Stream.of("One", "Two", "Three");
String[] array = stream.toArray(String[]::new);
```
另外,让我们考虑一个基元数组:
另外,让我们考虑一个原始数组:
```java
int[] integers = {2, 3, 4, 1};
......@@ -1106,7 +1106,7 @@ max = Math.max(max, elem);
...
```
假设我们有以下整数数组和一个名为`MathArrays`实用程序类,其中包含前面的方法:
假设我们有以下整数数组和一个名为`MathArrays`工具类,其中包含前面的方法:
```java
int[] integers = {2, 3, 4, 1, -4, 6, 2};
......@@ -1457,7 +1457,7 @@ public static void println(int[] arr) {
增加数组的大小并不简单。这是因为 Java 数组的大小是固定的,我们不能修改它们的大小。这个问题的解决方案需要创建一个具有所需大小的新数组,并将所有值从原始数组复制到这个数组中。这可以通过`Arrays.copyOf()`方法或`System.arraycopy()`(由`Arrays.copyOf()`内部使用)完成。
对于一个基元数组(例如,`int`),我们可以将数组的大小增加 1 后将值添加到数组中,如下所示:
对于一个原始数组(例如,`int`),我们可以将数组的大小增加 1 后将值添加到数组中,如下所示:
```java
public static int[] add(int[] arr, int item) {
......@@ -1810,7 +1810,7 @@ ImmutableArray<String> sample =
# 110 映射的默认值
在 JDK8 之前,这个问题的解决方案依赖于辅助方法,它基本上检查`Map`中给定键的存在,并返回相应的值或默认值。这种方法可以在实用程序类中编写,也可以通过扩展`Map`接口来编写。通过返回默认值,我们可以避免在`Map`中找不到给定键时返回`null`。此外,这是依赖默认设置或配置的方便方法。
在 JDK8 之前,这个问题的解决方案依赖于辅助方法,它基本上检查`Map`中给定键的存在,并返回相应的值或默认值。这种方法可以在工具类中编写,也可以通过扩展`Map`接口来编写。通过返回默认值,我们可以避免在`Map`中找不到给定键时返回`null`。此外,这是依赖默认设置或配置的方便方法。
从 JDK8 开始,这个问题的解决方案包括简单地调用`Map.getOrDefault()`方法。此方法获取两个参数,分别表示要在`Map`方法中查找的键和默认值。当找不到给定的键时,默认值充当应该返回的备份值。
......@@ -2289,7 +2289,7 @@ melons.put("refreshing", new Melon("Jade Dew", 3500));
melons.put("famous", new Melon("Cantaloupe", 1500));
```
现在,让我们来研究几种排序这个`Map`的解决方案。基本上,我们的目标是通过一个名为`Maps`实用程序类公开以下屏幕截图中的方法:
现在,让我们来研究几种排序这个`Map`的解决方案。基本上,我们的目标是通过一个名为`Maps`工具类公开以下屏幕截图中的方法:
![](img/68fea7e5-dde3-48c3-af33-d0701244c43e.png)
......@@ -3584,7 +3584,7 @@ private long prefixSum(int i) {
}
```
此外,我们还可以增加一个新的值:
此外,我们还可以增加一个新的值:
```java
public void add(int i, long v) {
......
......@@ -416,7 +416,7 @@ int path1compareToPath3 = path1.compareTo(path3); // 24
int path2compareToPath3 = path2.compareTo(path3); // 24
```
请注意,您可能会获得与上一示例不同的值。此外,在您的业务逻辑中,重要的是依赖于它们的含义,而不是依赖于它们的值(例如,说`if(path1compareToPath3 > 0) { ... }`,避免使用`if(path1compareToPath3 == 24) { ... }`)。
请注意,您可能会获得与上一示例不同的值。此外,在您的业务逻辑中,重要的是依赖于它们的含义,而不是依赖于它们的值(例如,说`if(path1compareToPath3 > 0) { ... }`,避免使用`if(path1compareToPath3 == 24) { ... }`)。
# 部分比较
......@@ -449,11 +449,11 @@ boolean ew = path1.endsWith("JavaModernChallenge.pdf"); // true
# 琐碎的文件夹遍历
实现`FileVisitor`接口需要重写它的四个方法。然而,NIO.2 附带了这个接口的一个内置的简单实现,称为`SimpleFileVisitor`。对于简单的情况,扩展这个类比实现`FileVisitor`更方便,因为它只允许我们重写必要的方法。
实现`FileVisitor`接口需要覆盖它的四个方法。然而,NIO.2 附带了这个接口的一个内置的简单实现,称为`SimpleFileVisitor`。对于简单的情况,扩展这个类比实现`FileVisitor`更方便,因为它只允许我们覆盖必要的方法。
例如,假设我们将电子课程存储在`D:/learning`文件夹的子文件夹中,我们希望通过`FileVisitor`API 访问每个子文件夹。如果在子文件夹的迭代过程中出现问题,我们只会抛出报告的异常。
为了塑造这种行为,我们需要重写`postVisitDirectory()`方法,如下所示:
为了塑造这种行为,我们需要覆盖`postVisitDirectory()`方法,如下所示:
```java
class PathVisitor extends SimpleFileVisitor<Path> {
......@@ -1112,7 +1112,7 @@ try (FileReader fr = new FileReader(chineseFile.toFile())) {
* `FileReader​(File file, Charset charset)`
* `FileReader​(String fileName, Charset charset)`
这一次,我们可以重写前面的代码片段并获得预期的输出:
这一次,我们可以覆盖前面的代码片段并获得预期的输出:
```java
try (FileReader frch = new FileReader(
......@@ -2260,7 +2260,7 @@ long mismatches14 = Files.mismatch(file1, file4); // 60
# 通过`FilenameFilter`过滤
`FilenameFilter`功能界面也可以用来过滤文件夹中的文件。首先,我们需要定义一个过滤器(例如,下面是 PDF 类型文件的过滤器):
`FilenameFilter`函数式接口也可以用来过滤文件夹中的文件。首先,我们需要定义一个过滤器(例如,下面是 PDF 类型文件的过滤器):
```java
String[] files = path.toFile().list(new FilenameFilter() {
......@@ -2322,7 +2322,7 @@ File[] folders = path.toFile().listFiles((File file)
File[] folders = path.toFile().listFiles(f -> f.isDirectory());
```
最后,我们可以通过会员参考
最后,我们可以通过成员引用
```java
File[] folders = path.toFile().listFiles(File::isDirectory);
......@@ -2577,7 +2577,7 @@ article name | color \ no. available items / size
这里,我们有几个分隔符(`&amp;`、`|`、`\``/`)和一个非常具体的格式。
现在,让我们来看看几个解决方案,它们将从这个文件中提取信息并将其标记为一个`List`。我们将在实用程序`FileTokenizer`中收集这些信息。
现在,让我们来看看几个解决方案,它们将从这个文件中提取信息并将其标记为一个`List`。我们将在工具`FileTokenizer`中收集这些信息。
`List`中获取物品的一种解决方案依赖于`String.split()`方法。基本上,我们必须逐行读取文件并对每行应用`String.split()`。每行分词的结果通过`List.addAll()`方法收集在`List`中:
......@@ -2860,7 +2860,7 @@ try (BufferedWriter bw = Files.newBufferedWriter(path,
}
```
可以通过`Formatter`类提供另一种解决方案。此类专用于格式化字符串,并使用与`String.format()`相同的格式化规则。它有一个`format()`方法,我们可以用它重写前面的代码片段:
可以通过`Formatter`类提供另一种解决方案。此类专用于格式化字符串,并使用与`String.format()`相同的格式化规则。它有一个`format()`方法,我们可以用它覆盖前面的代码片段:
```java
Path path = Paths.get("withformatter.txt");
......
......@@ -604,7 +604,7 @@ Car carViaIdNameColorCnstr = idNameColorCnstr
# 通过私有构造器实例化类
Java 反射 API 也可以通过其`private`构造器来实例化类。例如,假设我们有一个名为`Cars`实用程序类。按照最佳实践,我们将此类定义为`final`,并使用`private`构造器来禁止实例:
Java 反射 API 也可以通过其`private`构造器来实例化类。例如,假设我们有一个名为`Cars`工具类。按照最佳实践,我们将此类定义为`final`,并使用`private`构造器来禁止实例:
```java
public final class Cars {
......@@ -849,7 +849,7 @@ public class Melon implements Comparator<Melon> {
}
```
在这里,我们实现`Comparator`接口并重写`compare()`方法。此外,我们明确规定了`compare()`方法需要两个`Melon`实例。编译器将继续执行*类型擦除*,并创建一个包含两个对象的新方法,如下所示:
在这里,我们实现`Comparator`接口并覆盖`compare()`方法。此外,我们明确规定了`compare()`方法需要两个`Melon`实例。编译器将继续执行*类型擦除*,并创建一个包含两个对象的新方法,如下所示:
```java
public int compare(Object m1, Object m2) {
......@@ -922,7 +922,7 @@ public interface Slicer {
}
```
现在,`Slicer`的任何实现都必须实现`type()`方法,并且可以选择性地重写`slice()`方法或依赖于默认实现。
现在,`Slicer`的任何实现都必须实现`type()`方法,并且可以选择性地覆盖`slice()`方法或依赖于默认实现。
Java 反射 API 可以通过`Method.isDefault()`标志方法识别`default`方法:
......@@ -1328,7 +1328,7 @@ private static Map<String, Class<?>>
}
```
到目前为止,我们知道哪些领域没有获取器和设置器。它们的名称和类型存储在地图中。让我们循环映射并生成获取器:
到目前为止,我们知道哪些字段没有获取器和设置器。它们的名称和类型存储在地图中。让我们循环映射并生成获取器:
```java
public static StringBuilder generateGetters(Class<?> clazz) {
......@@ -1447,7 +1447,7 @@ Java 注释从 Java 反射 API 得到了很多关注。让我们看看几种用
# 检查包注解
`package-info.java`中添加了特定于包的注释,如下面的屏幕截图所示。在这里,`modern.challenge`包被注释为`@Packt`
`package-info.java`中添加了特定于包的注释,如下面的屏幕截图所示。在这里,`modern.challenge`包被注释为`@Packt`
![](img/e48d485d-b2c7-4def-85a2-8f297f863e6e.png)
......@@ -1499,7 +1499,7 @@ Fruit fruitAnnotation = clazz.getDeclaredAnnotation(Fruit.class);
# 检查方法注解
我们来看看`Melon`类中`eat()`方法的`@Ripe`
我们来看看`Melon`类中`eat()`方法的`@Ripe`
![](img/a62edba6-1f84-46b8-9c24-dda5c288f30f.png)
......@@ -1646,7 +1646,7 @@ Field weightField = clazz.getDeclaredField("weight");
Annotation[] fieldAnnotations = weightField.getDeclaredAnnotations();
```
获取`@Unit`的值可以如下所示:
获取`@Unit`的值可以如下所示:
```java
Unit unitFieldAnnotation = (Unit) fieldAnnotations[0];
......
......@@ -43,11 +43,11 @@ public class Melon {
}
```
假设我们有一个客户——我们叫他马克——他想开一家卖瓜的公司。我们根据他的描述塑造了前面的班级。他的主要目标是拥有一个库存应用来支持他的想法和决策,因此需要创建一个必须基于业务需求和发展的应用。我们将在下面几节中查看每天开发此应用所需的时间。
假设我们有一个客户——我们叫他马克——他想开一家卖瓜的公司。我们根据他的描述塑造了前面的。他的主要目标是拥有一个库存应用来支持他的想法和决策,因此需要创建一个必须基于业务需求和发展的应用。我们将在下面几节中查看每天开发此应用所需的时间。
# 第 1 天(按瓜的类型过滤)
有一天,马克让我们提供一个功能,可以按瓜的类型过滤瓜。因此,我们创建了一个名为`Filters`实用程序类,并实现了一个`static`方法,该方法将瓜列表和要过滤的类型作为参数。
有一天,马克让我们提供一个功能,可以按瓜的类型过滤瓜。因此,我们创建了一个名为`Filters`工具类,并实现了一个`static`方法,该方法将瓜列表和要过滤的类型作为参数。
得到的方法非常简单:
......@@ -249,7 +249,7 @@ public interface Predicate<T> {
}
```
接下来,我们重写`filterMelons()`方法并将其重命名为`filter()`
接下来,我们覆盖`filterMelons()`方法并将其重命名为`filter()`
```java
public static <T> List<T> filter(
......@@ -282,7 +282,7 @@ List<Integer> smallThan10 = Filters
.filter(numbers, (Integer i) -> i < 10);
```
退后一步,看看我们的起点和现在。由于 Java8 函数式接口和 Lambda 表达式,这种差异是巨大的。你注意到`Predicate`界面上的`@FunctionalInterface`注释了吗?好吧,这是一个信息注释类型,用于标记函数式接口。如果标记的接口不起作用,则发生错误是很有用的。
退后一步,看看我们的起点和现在。由于 Java8 函数式接口和 Lambda 表达式,这种差异是巨大的。你注意到`Predicate`接口上的`@FunctionalInterface`注解了吗?好吧,这是一个信息注释类型,用于标记函数式接口。如果标记的接口不起作用,则发生错误是很有用的。
从概念上讲,函数式接口只有一个抽象方法。此外,我们定义的`Predicate`接口已经作为`java.util.function.Predicate`接口存在于 Java8 中。`java.util.function`包包含 40 多个这样的接口。因此,在定义一个新的包之前,最好检查这个包的内容。大多数情况下,六个标准的内置函数式接口就可以完成这项工作。具体如下:
......@@ -373,7 +373,7 @@ public interface ScannerDoubleFunction {
声明函数式接口只是解决方案的一半。
2. 到目前为止,我们可以编写一个`Scanner -> double`类型的 Lambda,但是我们需要一个接收并执行它的方法。为此,让我们考虑一下`Doubles`实用程序类中的以下方法:
2. 到目前为止,我们可以编写一个`Scanner -> double`类型的 Lambda,但是我们需要一个接收并执行它的方法。为此,让我们考虑一下`Doubles`工具类中的以下方法:
```java
public static double read(ScannerDoubleFunction snf)
......@@ -464,7 +464,7 @@ private static final Map<String, Supplier<Fruit>> MELONS
"Cantaloupe", Cantaloupe::new);
```
此外,我们可以重写`newInstance()`方法来使用这个映射:
此外,我们可以覆盖`newInstance()`方法来使用这个映射:
```java
public static Fruit newInstance(Class<?> clazz) {
......@@ -570,7 +570,7 @@ public class WhitespacesRemover implements RemoveStrategy {
}
```
最后,让我们定义一个实用程序类作为策略的入口点:
最后,让我们定义一个工具类作为策略的入口点:
```java
public final class Remover {
......@@ -594,7 +594,7 @@ String noNr = Remover.remove(text, new NumberRemover());
但是我们真的需要`NumberRemover``WhitespacesRemover`类吗?我们是否需要为进一步的策略编写类似的类?显然,答案是否定的。
再次查看我们的界面
再次查看我们的接口
```java
@FunctionalInterface
......@@ -668,7 +668,7 @@ public class GreekPizza extends PizzaMaker {
}
```
因此,每种类型的披萨都需要一个新类来重写`addTopIngredients()`方法。最后,我们可以这样做比萨饼:
因此,每种类型的披萨都需要一个新类来覆盖`addTopIngredients()`方法。最后,我们可以这样做比萨饼:
```java
Pizza nPizza = new Pizza();
......@@ -799,7 +799,7 @@ fireStation.notifyFireStations(
这是*样板*代码的另一个经典案例。每个地方消防站都需要一个新的类和实现`fire()`方法。
不过,兰博达斯可以再次帮助我们!查看`FireObserver`界面。它只有一个抽象方法;因此,这是一个函数式接口:
不过,兰博达斯可以再次帮助我们!查看`FireObserver`接口。它只有一个抽象方法;因此,这是一个函数式接口:
```java
@FunctionalInterface
......@@ -1090,7 +1090,7 @@ public class CakeDecorator {
}
```
门课主要完成两件事:
个类主要完成两件事:
* 在构造器中,它调用`reduceDecorations()`方法。此方法将通过`Stream.reduce()``Function.andThen()`方法链接传递的`Function`数组。结果是由给定的`Function`数组组成的单个`Function`
* 当组合的`Function``apply()`方法从`decorate()`方法调用时,它将逐一应用给定函数的链。由于给定数组中的每一个`Function`都是一个修饰符,因此合成的`Function`将逐个应用每个修饰符。
......@@ -1252,7 +1252,7 @@ sequence.recordSequence(new DeleteCommand(hd));
sequence.runSequence();
```
显然,我们这里有很多*样板*代码。查看命令类。我们真的需要所有这些课程吗?好吧,如果我们意识到`Command`接口实际上是一个函数式接口,那么我们可以删除它的实现并通过 Lambda 提供行为(命令类只是行为块,因此它们可以通过 Lambda 表示),如下所示:
显然,我们这里有很多*样板*代码。查看命令类。我们真的需要所有这些吗?好吧,如果我们意识到`Command`接口实际上是一个函数式接口,那么我们可以删除它的实现并通过 Lambda 提供行为(命令类只是行为块,因此它们可以通过 Lambda 表示),如下所示:
```java
HardDisk hd = new HardDisk();
......
......@@ -1086,7 +1086,7 @@ int result = ints.stream()
所有这些方法都以一个`Predicate`作为参数,并针对它获取一个`boolean`结果。
这三种操作依赖于*短路*技术。换句话说,在我们处理整个流之前,这些方法可能会返回。例如,如果`allMatch()`匹配`false`(将给定的`Predicate`评估`false`,则没有理由继续。最终结果为`false`
这三种操作依赖于*短路*技术。换句话说,在我们处理整个流之前,这些方法可能会返回。例如,如果`allMatch()`匹配`false`(将给定的`Predicate`求值`false`,则没有理由继续。最终结果为`false`
假设我们将以下列表包装在流中:
......@@ -1209,7 +1209,7 @@ int min = melons.stream()
# 归约
`sum()``max()``min()`被称为*归约*的特例。我们所说的*归约*,是指基于两个主要陈述的抽象:
`sum()``max()``min()`被称为*归约*的特例。我们所说的*归约*,是指基于两个主要语句的抽象:
* 取初始值(`T`
* 取一个`BinaryOperator<T>`将两个元素结合起来,产生一个新的值
......@@ -1555,7 +1555,7 @@ double sumWeightsKg = melons.stream()
* `reducing​(T identity, BinaryOperator<T> op)`
* `reducing​(U identity, Function<? super T,​? extends U> mapper, BinaryOperator<U> op)`
`reducing()`论点很直截了当。我们有用于减少的`identity`值(以及没有输入元素时返回的值)、应用于每个输入值的映射函数和用于减少映射值的函数。
`reducing()`参数很直截了当。我们有用于减少的`identity`值(以及没有输入元素时返回的值)、应用于每个输入值的映射函数和用于减少映射值的函数。
例如,让我们通过`reducing()`重写前面的代码片段。请注意,我们从 0 开始求和,通过映射函数将其从克转换为千克,并通过 Lambda 减少值(结果千克):
......@@ -2808,7 +2808,7 @@ public class MelonComparator implements Comparator {
}
```
现在,我们可以参考一下:
现在,我们可以引用一下:
* 无方法引用:
......@@ -3116,7 +3116,7 @@ public static <T> Stream<T> elementAsStream(T element) {
}
```
如果这个方法存在于名为`AsStreams`实用程序类中,那么我们可以执行几个调用,如下所示:
如果这个方法存在于名为`AsStreams`工具类中,那么我们可以执行几个调用,如下所示:
```java
// 0
......@@ -3330,7 +3330,7 @@ Comparator<Melon> byWeight
到目前为止,一切都很好!
现在,假设我们想按重量和类型对列表进行排序。换言之,当两个瓜的重量相同时(例如,`Horned (1600g)``Hemi(1600g`),它们应该按类型分类(例如,`Hemi(1600g)``Horned(1600g)`)。天真的方法如下所示:
现在,假设我们想按重量和类型对列表进行排序。换言之,当两个瓜的重量相同时(例如,`Horned (1600g)``Hemi(1600g`),它们应该按类型分类(例如,`Hemi(1600g)``Horned(1600g)`)。朴素的方法如下所示:
```java
// Apollo(3000g), Gac(2000g), Gac(3000g), Hemi(1600g), Horned(1600g)
......@@ -3433,7 +3433,7 @@ double resultfg = fg.apply(4d); // 32.0
默认方法被添加到 Java8 中。它们的主要目标是为接口提供支持,以便它们能够超越抽象契约(仅包含抽象方法)而发展。对于编写库并希望以兼容的方式发展 API 的人来说,这个工具非常有用。通过默认方法,可以在不中断现有实现的情况下丰富接口。
界面直接实现默认方法,并通过`default`关键字进行识别。
接口直接实现默认方法,并通过`default`关键字进行识别。
例如,以下接口定义了一个抽象方法`area()`,默认方法称为`perimeter()`
......@@ -3471,7 +3471,7 @@ public class Square implements Polygon {
其他多边形(例如矩形和三角形)可以实现`Polygon`,并基于通过默认实现计算的周长来表示面积。
但是,在某些情况下,我们可能需要重写默认方法的默认实现。例如,`Square`类可以重写`perimeter()`方法,如下所示:
但是,在某些情况下,我们可能需要覆盖默认方法的默认实现。例如,`Square`类可以覆盖`perimeter()`方法,如下所示:
```java
@Override
......
......@@ -595,7 +595,7 @@ ScheduledExecutorService executor
![](img/3cf100e5-7ad8-4548-9a33-1e55a146b655.png)
我们可以将装配线视为我们工厂的助手,因此它可以实现为助手或实用程序类(当然,它也可以很容易地切换到非`static`实现,因此如果对您的情况更有意义,请随意切换):
我们可以将装配线视为我们工厂的助手,因此它可以实现为助手或工具类(当然,它也可以很容易地切换到非`static`实现,因此如果对您的情况更有意义,请随意切换):
```java
public final class AssemblyLine {
......@@ -1057,7 +1057,7 @@ private static class Consumer implements Runnable {
不要混淆`nrOfConsumers``threadGroup.activeCount()``nrOfConsumers`变量存储当前打包灯泡的使用者(线程)的数量,而`threadGroup.activeCount()`表示所有活动使用者(线程),包括那些当前不工作(空闲)并且正等待从缓存中重用或调度的使用者(线程)。
现在,在一个真实的案例中,一个主管将监控装配线,当他们注意到当前数量的消费者无法面对即将到来的涌入时,他们将呼叫更多的消费者加入(最多允许 50 个消费者)。此外,当他们注意到一些消费者只是停留在附近,他们会派遣他们到其他工作。下图是此场景的图形表示:
现在,在一个真实的案例中,一个主管将监控装配线,当他们注意到当前数量的消费者无法面对即将到来的涌入时,他们将调用更多的消费者加入(最多允许 50 个消费者)。此外,当他们注意到一些消费者只是停留在附近,他们会派遣他们到其他工作。下图是此场景的图形表示:
![](img/a7ddcb56-2877-4575-96ed-e3e9b7aef403.png)
......@@ -1433,7 +1433,7 @@ private static class Producer implements Callable {
}
```
执行者服务可以通过`submit()`方法向`Callable`提交任务,但不知道提交任务的结果何时可用。因此,`Callable`立即返回一个特殊类型,名为`Future`。异步计算的结果由`Future`表示,通过`Future`可以在任务可用时获取任务结果。从概念上讲,我们可以将`Future`看作 JavaScript 承诺,或者是在稍后时间点进行的计算的结果。现在,我们创建一个`Producer`提交给`Callable`
执行者服务可以通过`submit()`方法向`Callable`提交任务,但不知道提交任务的结果何时可用。因此,`Callable`立即返回一个特殊类型,名为`Future`。异步计算的结果由`Future`表示,通过`Future`可以在任务可用时获取任务结果。从概念上讲,我们可以将`Future`看作 JavaScript `Promise`,或者是在稍后时间点进行的计算的结果。现在,我们创建一个`Producer`提交给`Callable`
```java
String bulb = "bulb-" + rnd.nextInt(1000);
......@@ -2308,7 +2308,7 @@ for (int i = 1; i <= 10; i++) {
为了达到当前阶段(障碍),等待其他方到达,需要调用`arriveAndAwaitAdvance()`方法。这种方法将阻止所有登记方到达当前阶段。一旦最后一注册方到达本阶段,各方将进入`Phaser`的下一阶段。
或者,当所有注册方到达当前阶段时,我们可以通过重写`onAdvance()`方法`onAdvance​(int phase, int registeredParties)`来运行特定操作。如果要触发`Phaser`的终止,则此方法返回一个`boolean`值,即`true`。另外,我们可以通过`forceTermination()`强制终止,也可以通过`isTerminated()`的标志方法进行测试。重写`onAdvance()`方法需要我们扩展`Phaser`类(通常通过匿名类)。
或者,当所有注册方到达当前阶段时,我们可以通过覆盖`onAdvance()`方法`onAdvance​(int phase, int registeredParties)`来运行特定操作。如果要触发`Phaser`的终止,则此方法返回一个`boolean`值,即`true`。另外,我们可以通过`forceTermination()`强制终止,也可以通过`isTerminated()`的标志方法进行测试。覆盖`onAdvance()`方法需要我们扩展`Phaser`类(通常通过匿名类)。
现在,我们应该有足够的细节来解决我们的问题。因此,我们必须在`Phaser`的三个阶段中模拟服务器的启动过程。服务器被认为是在其五个内部服务启动之后启动并运行的。在第一阶段,我们需要同时启动三个服务。在第二阶段,我们需要同时启动另外两个服务(只有在前三个服务已经运行的情况下才能启动)。在第三阶段,服务器执行最后一次签入,并被视为已启动并正在运行。
......@@ -2334,7 +2334,7 @@ public class ServerInstance implements Runnable {
}
```
使用匿名类,我们创建这个`Phaser`对象并重写`onAdvance()`方法来定义一个有两个主要目的的操作:
使用匿名类,我们创建这个`Phaser`对象并覆盖`onAdvance()`方法来定义一个有两个主要目的的操作:
* 打印当前阶段的快速状态和注册方的数量
* 如果没有注册方,触发`Phaser`终止
......
......@@ -148,7 +148,7 @@ thread.interrupt();
...
```
这一次,`while`块将永远运行,因为它的保护条件总是被评估`true`
这一次,`while`块将永远运行,因为它的保护条件总是被求值`true`
代码不能作用于中断,因此输出如下:
......@@ -925,7 +925,7 @@ public static void deliverOrderNotifyCustomer() {
![](img/d301cd32-8885-4e5b-bc16-c09c8a197d2c.png)
因此,在`supplyAsync()`中,如果发生异常,则不会调用以下回调。此外,未来将得到解决,但这一例外情况除外。相同的规则适用于每个回调。如果第一个`thenApply()`出现异常,则不调用以下`thenApply()``thenAccept()`
因此,在`supplyAsync()`中,如果发生异常,则不会调用以下回调。此外,`Future`将得到解决,但这一异常除外。相同的规则适用于每个回调。如果第一个`thenApply()`出现异常,则不调用以下`thenApply()``thenAccept()`
如果我们试图计算订单总数的结果是一个`IllegalStateException`,那么我们可以依赖`exceptionally()`回调,这给了我们一个恢复的机会。此方法接受一个`Function<Throwable,​? extends T>`,并返回一个`CompletionStage<T>`,因此返回一个`CompletableFuture`。让我们在工作中看看:
......@@ -1257,7 +1257,7 @@ public static void fetchOrderTotalHandle() {
注意,`res`将是`null`;否则,如果发生异常,`ex`将是`null`
如果我们需要在例外情况下完成,那么我们可以通过`completeExceptionally()`继续,如下例所示:
如果我们需要在异常下完成,那么我们可以通过`completeExceptionally()`继续,如下例所示:
```java
CompletableFuture<Integer> cf = new CompletableFuture<>();
......@@ -1495,9 +1495,9 @@ List<String> results = cfDownloaded.thenApply(e -> {
}).get();
```
`join()`方法类似于`get()`,但是,如果基础`CompletableFuture`异常完成,则抛出未检查的异常。
`join()`方法类似于`get()`,但是,如果基础`CompletableFuture`异常完成,则抛出非受检异常。
由于我们在所有相关`CompletableFuture`完成后呼叫`join()`,因此没有阻塞点。
由于我们在所有相关`CompletableFuture`完成后调用`join()`,因此没有阻塞点。
返回的`List<String>`包含调用`downloadInvoices()`方法得到的结果,如下所示:
......@@ -2094,7 +2094,7 @@ public class AtomicAccumulator implements Runnable {
# 222 重入锁
`Lock`接口包含一组锁定操作,可以显式地用于微调锁定过程(它提供比内在锁定更多的控制)。其中,我们有轮询、无条件、定时和可中断的锁获取。基本上,`Lock`用附加功能公开了`synchronized`关键字的未来`Lock`接口如下图所示:
`Lock`接口包含一组锁定操作,可以显式地用于微调锁定过程(它提供比内在锁定更多的控制)。其中,我们有轮询、无条件、定时和可中断的锁获取。基本上,`Lock`用附加功能公开了`synchronized`关键字的`Future``Lock`接口如下图所示:
```java
public interface Lock {
......
......@@ -118,7 +118,7 @@ public String findStatus() {
}
```
但要记住,即使涉及的`Optional`类不是空的,也要对`orElse()`进行评估。换句话说,`orElse()`即使不使用它的值也会被评估。既然如此,最好只在其参数是已经构造的值时才依赖`orElse()`。这样,我们就可以减轻潜在的性能惩罚。下一个问题是`orElse()`不是正确的选择时解决的。
但要记住,即使涉及的`Optional`类不是空的,也要对`orElse()`进行求值。换句话说,`orElse()`即使不使用它的值也会被求值。既然如此,最好只在其参数是已经构造的值时才依赖`orElse()`。这样,我们就可以减轻潜在的性能惩罚。下一个问题是`orElse()`不是正确的选择时解决的。
# 229 返回不存在的默认值
......@@ -200,7 +200,7 @@ public String findStatus() {
}
```
或者,另一个例外是,例如,`IllegalStateException`
或者,另一个异常是,例如,`IllegalStateException`
```java
// Prefer
......@@ -225,11 +225,11 @@ public String findStatus() {
}
```
然而,要注意,在生产中抛出未经检查的异常而不带有意义的消息是不好的做法。
然而,要注意,在生产中抛出非受检的异常而不带有意义的消息是不好的做法。
# 231 `Optional`和空引用
在某些情况下,可以使用接受`null`参考的方法来利用`orElse(null)`
在某些情况下,可以使用接受`null`引用的方法来利用`orElse(null)`
本场景的候选对象是 Java 反射 API`Method.invoke()`(见第 7 章、“Java 反射类、接口、构造器、方法、字段”。
......@@ -276,11 +276,11 @@ if (bookInstance.isPresent()) {
method.invoke(bookInstance.orElse(null));
```
根据经验,只有当我们有`Optional`并且需要`null`参考时,才应该使用`orElse(null)`。否则,请避开`orElse(null)`
根据经验,只有当我们有`Optional`并且需要`null`引用时,才应该使用`orElse(null)`。否则,请避开`orElse(null)`
# 232 使用当前`Optional`类
有时候,我们想要的只是消费一节课。如果`Optional`不存在,则无需进行任何操作。不熟练的解决方案将依赖于`isPresent()`-`get()`对,如下所示:
有时候,我们想要的只是消费一个类。如果`Optional`不存在,则无需进行任何操作。不熟练的解决方案将依赖于`isPresent()`-`get()`对,如下所示:
```java
// Avoid
......
......@@ -806,7 +806,7 @@ System.out.println("Status code: " + responseOfFile.statusCode());
System.out.println("Body: " + responseOfFile.body());
```
如需指定开放式选项,请呼叫`ofFile(Path file, OpenOption... openOptions)`
如需指定开放式选项,请调用`ofFile(Path file, OpenOption... openOptions)`
# 将响应体作为字节数组处理
......@@ -864,7 +864,7 @@ System.out.println("Body: "
然而,我们习惯于将 JSON 数据表示为 Java 对象(POJO),并在需要时依赖于 JSON 和 Java 之间的转换。我们可以为我们的问题编写一个解决方案,而不涉及 HTTP 客户端 API。但是,我们也可以使用`HttpResponse.BodyHandler`的自定义实现编写一个解决方案,该实现依赖于 JSON 解析器将响应转换为 Java 对象。例如,我们可以依赖 JSON-B(在第 6 章中介绍,“Java I/O 路径、文件、缓冲区、扫描和格式化”中)。
实现`HttpResponse.BodyHandler`接口意味着重写`apply(HttpResponse.ResponseInfo responseInfo)`方法。使用这种方法,我们可以从响应中获取字节,并将它们转换为 Java 对象。代码如下:
实现`HttpResponse.BodyHandler`接口意味着覆盖`apply(HttpResponse.ResponseInfo responseInfo)`方法。使用这种方法,我们可以从响应中获取字节,并将它们转换为 Java 对象。代码如下:
```java
public class JsonBodyHandler<T>
......
......@@ -33,7 +33,7 @@ JIT 使用代码执行的统计信息来优化代码,这也很有趣。例如
这些程序仍然是用汇编或 C 语言编写的。对于我们其他人来说,我们有 Java,尽管这对许多专业人士来说似乎很奇怪,但即使是几乎实时的程序,如高频交易应用,也是用 Java 编写的。
这些应用通过网络连接到证券交易所,它们根据市场变化在毫秒内进行股票买卖。Java 能够做到这一点。执行编译的 Java 代码所需的 Java 运行时环境(也包括 JVM 本身)包含允许 Java 程序访问网络、磁盘上的文件和其他资源的代码。为此,运行时包含代码可以实例化、执行的高级类,以及执行低级作业的高级类。你也要这样做。这意味着实际的 Java 代码不需要处理 IP 包、TCP 连接,甚至当它想要在某些微服务架构中使用或提供 REST 服务时,也不需要处理 HTTP。它已经在运行库中实现,应用程序员所要做的就是在代码中包含类,并在与程序匹配的抽象级别上使用它们提供的 API。当你用 Java 编程时,你可以专注于你想要解决的实际问题,那就是*业务*代码,而不是底层的系统代码。如果它不在标准库中,您将在某个外部库中的某个产品中找到它,并且您很可能会找到解决该问题的开源解决方案。
这些应用通过网络连接到证券交易所,它们根据市场变化在毫秒内进行股票买卖。Java 能够做到这一点。执行编译的 Java 代码所需的 Java 运行时环境(也包括 JVM 本身)包含允许 Java 程序访问网络、磁盘上的文件和其他资源的代码。为此,运行时包含代码可以实例化、执行的高级类,以及执行低级作业的高级类。你也要这样做。这意味着实际的 Java 代码不需要处理 IP 包、TCP 连接,甚至当它想要在某些微服务架构中使用或提供 REST 服务时,也不需要处理 HTTP。它已经在运行库中实现,程序员所要做的就是在代码中包含类,并在与程序匹配的抽象级别上使用它们提供的 API。当你用 Java 编程时,你可以专注于你想要解决的实际问题,那就是*业务*代码,而不是底层的系统代码。如果它不在标准库中,您将在某个外部库中的某个产品中找到它,并且您很可能会找到解决该问题的开源解决方案。
这也是 Java 的一个优点。有大量的开源库可用于各种不同的用途。如果您找不到适合您的问题的库,并且开始编写一些低级代码,那么您可能是做错了什么。本书中的一些主题很重要,比如类加载器或反射,不是因为你必须每天使用它们,而是因为它们被框架使用,了解它们有助于你理解这些框架是如何工作的。如果不使用反射或直接编写自己的类加载器或程序多线程就无法解决问题,那么您可能选择了错误的框架。几乎可以肯定有一个很好的例子:ApacheCommons 、Google 和软件行业的许多其他重要参与者将其 Java 库发布为开源。
......@@ -181,7 +181,7 @@ Matching Java Virtual Machines (13):
export JAVA_HOME=$(/usr/libexec/java_home)
```
您可以将此文件放入`.bashrc`文件中,每次启动终端应用时都会执行该文件,因此`JAVA_HOME`始终设置。如果您想使用不同版本,可以使用`-v`,这次使用小写选项,到同一个实用程序,如下所示:
您可以将此文件放入`.bashrc`文件中,每次启动终端应用时都会执行该文件,因此`JAVA_HOME`始终设置。如果您想使用不同版本,可以使用`-v`,这次使用小写选项,到同一个工具,如下所示:
```java
export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
......@@ -602,7 +602,7 @@ Hello World
我们编辑的文件只包含代码片段,我们删除了大部分行,除了`main`方法的声明,并在其周围插入了类的声明。
在 Java 中,不能像在许多其他语言中那样拥有独立的方法或函数。每个方法都属于某个类,每个类都应该在一个单独的文件中声明(好吧,差不多,但现在,让我们跳过异常)。文件名必须与类名相同。编译器对`public`类要求这样。即使是非公立的课程,我们也通常遵循这个惯例。如果您将文件从`HelloWorld.java`重命名为`Hello.java`,则当您尝试用新名称编译文件时,编译器将显示一个错误:
在 Java 中,不能像在许多其他语言中那样拥有独立的方法或函数。每个方法都属于某个类,每个类都应该在一个单独的文件中声明(好吧,差不多,但现在,让我们跳过异常)。文件名必须与类名相同。编译器对`public`类要求这样。即使是非公共类,我们也通常遵循这个惯例。如果您将文件从`HelloWorld.java`重命名为`Hello.java`,则当您尝试用新名称编译文件时,编译器将显示一个错误:
```java
$ mv HelloWorld.java Hello.java
......@@ -615,9 +615,9 @@ public class HelloWorld {
那么,让我们把它移回原来的名字,也就是,`mv Hello.java HelloWorld.java`
类的声明以`class`关键字开始,然后是类的名称,一个大括号开始,直到匹配的大括号结束。中间的一切都属于班级
类的声明以`class`关键字开始,然后是类的名称,一个大括号开始,直到匹配的大括号结束。中间的一切都属于
现在,让我们跳过为什么我在课堂前写了`public`,重点讨论其中的主要方法。该方法不返回任何值,因此返回值为`void`。参数,名为`args`,是一个字符串数组。当 JVM 启动`main`方法时,它将命令行参数传递给这个数组中的程序。然而,这次我们没有用。`main`方法包含打印出`Hello World`的行。现在,让我们再检查一下这条线。
现在,让我们跳过为什么我在类前写了`public`,重点讨论其中的`main`方法。该方法不返回任何值,因此返回值为`void`。参数,名为`args`,是一个字符串数组。当 JVM 启动`main`方法时,它将命令行参数传递给这个数组中的程序。然而,这次我们没有用。`main`方法包含打印出`Hello World`的行。现在,让我们再检查一下这条线。
在其他语言中,将内容打印到控制台只需要一个`print`语句,或者一个非常类似的命令。我记得有些初级解释器甚至允许我们输入`?`而不是`print`,因为在屏幕上打印是很常见的。这在过去的 40 年里已经发生了很大的变化。我们使用图形屏幕、互联网和许多其他输入和输出通道。现在,在控制台上写东西已经不是很常见了。
......
......@@ -121,7 +121,7 @@ Ant可从其[官方网站](http://ant.apache.org)下载。您可以下载源或
![](img/5547972d-0d49-4922-a323-964ebb767050.png)
可以使用以下命令行检查下载的文件:`$ md5 apache-ant-1.9.7-bin.tar.gz MD5 (apache-ant-1.9.7-bin.tar.gz) = bc1d9e5fe73eee5c50b26ed411fb0119`计算出的 MD5 校验和与网站上的相同,说明文件完整性没有受到损害。在 Windows 操作系统上,没有计算 MD5 摘要的工具。微软提供了一个工具,叫做**文件完整性校验和验证工具**,可以在[这个页面](https://support.microsoft.com/en-us/help/841290/availability-and-description-of-the-file-checksum-integrity-verifier-utility)上找到。如果您使用 Linux,可能会发生未安装`md5``md5sum`实用程序的情况。在这种情况下,您可以使用`apt-get`命令或 Linux 发行版支持的任何安装工具来安装它。
可以使用以下命令行检查下载的文件:`$ md5 apache-ant-1.9.7-bin.tar.gz MD5 (apache-ant-1.9.7-bin.tar.gz) = bc1d9e5fe73eee5c50b26ed411fb0119`计算出的 MD5 校验和与网站上的相同,说明文件完整性没有受到损害。在 Windows 操作系统上,没有计算 MD5 摘要的工具。微软提供了一个工具,叫做**文件完整性校验和验证工具**,可以在[这个页面](https://support.microsoft.com/en-us/help/841290/availability-and-description-of-the-file-checksum-integrity-verifier-utility)上找到。如果您使用 Linux,可能会发生未安装`md5``md5sum`工具的情况。在这种情况下,您可以使用`apt-get`命令或 Linux 发行版支持的任何安装工具来安装它。
下载文件后,可以使用以下命令将其分解为子目录:
......@@ -634,7 +634,7 @@ Integer a = 113;
有一个特殊的类,叫`String`。此类型的对象包含字符。`String`没有原始对应物,但我们使用它很多次,就像是原始类型,它不是。它在 Java 程序中无处不在,并且有一些语言构造,例如直接与这种类型一起工作的字符串连接。
原始类型和对象之间的主要区别在于原始类型不能用来调用它们的方法。它们只是价值观。当我们创建并发程序时,它们不能用作锁。另一方面,它们消耗更少的内存。内存消耗与其对速度的影响之间的差异非常重要,尤其是当我们有一个值数组时。
原始类型和对象之间的主要区别在于原始类型不能用来调用它们的方法。它们只是。当我们创建并发程序时,它们不能用作锁。另一方面,它们消耗更少的内存。内存消耗与其对速度的影响之间的差异非常重要,尤其是当我们有一个值数组时。
# 数组
......@@ -700,7 +700,7 @@ for( initial expression ; condition ; increment expression )
block
```
首先,计算初始表达式。它可能包含变量声明,如我们的示例所示。前例中的`j`变量仅在循环块内可见。之后,将评估条件,在执行块之后,执行增量表达式。只要条件为真,循环就会重复。如果在执行初始表达式之后条件为`false`,则循环根本不会执行。该块是一个用分号分隔的命令列表,并在`{``}`字符之间封闭。
首先,计算初始表达式。它可能包含变量声明,如我们的示例所示。前例中的`j`变量仅在循环块内可见。之后,将求值条件,在执行块之后,执行增量表达式。只要条件为真,循环就会重复。如果在执行初始表达式之后条件为`false`,则循环根本不会执行。该块是一个用分号分隔的命令列表,并在`{``}`字符之间封闭。
封闭块 Java 代替了`{``}`,它允许您在`for`循环头之后使用单个命令。在`while`循环的情况下也是如此,对于`if...else`构造也是如此。实践表明,这不是专业人士应该使用的。专业代码总是使用大括号,即使只有一个命令块在适当的位置。这就避免了悬空`else`问题,通常使代码更具可读性。这类似于许多 C 语言。它们中的大多数都允许在这些地方使用单个命令,而专业程序员为了可读性的目的避免在这些语言中使用单个命令。讽刺的是,在这些地方,唯一严格要求使用`{``}`大括号的语言是 Perl—一种因代码不可读而臭名昭著的语言。
......@@ -780,7 +780,7 @@ Java 语言中有一些我们不使用的特性。当语言被创建时,这些
类是使用`class`关键字定义的,每个类都必须有一个名称。名称在包中应该是唯一的(请参阅下一节),并且必须与文件名相同。一个类可以实现一个接口或扩展另一个类,我们将在后面看到一个示例。类也可以是`abstract``final``public`。这些是用适当的关键字定义的,您将在下面的示例中看到。
我们的课程有两个班。它们都是`public``public`类可以从任何地方访问。不是`public`的类只在包内可见。内部类和嵌套类也可以`private`仅在文件级定义的顶级类中可见。
我们的项目有两个类。它们都是`public``public`类可以从任何地方访问。不是`public`的类只在包内可见。内部类和嵌套类也可以`private`仅在文件级定义的顶级类中可见。
包含要由 Java 环境调用的`main()`方法的类应该是`public`。这是因为它们是由 JVM 调用的。
......@@ -834,7 +834,7 @@ InnerClass ic = tl.new InnerClass();
匿名类是在一个命令中定义和实例化的。它们是嵌套、内部或本地类的一种短形式,以及类的实例化。匿名类总是实现接口或扩展命名类。新关键字后面是接口的名称或类,在括号之间的构造器中包含参数列表。定义匿名类主体的块在构造器调用之后立即站在后面。在扩展接口的情况下,构造器可以是唯一没有参数的构造器。没有名称的匿名类不能有自己的构造器。在现代 Java 中,我们通常使用 Lambda 而不是匿名类。
最后但同样重要的是,实际上,至少我应该提到嵌套类和内部类也可以嵌套在更深的结构中。内部类不能包含嵌套类,但嵌套类可以包含内部类。为什么?我从来没有遇到过谁能可靠地告诉我真正的原因。没有建筑上的原因。可能是这样的。Java 不允许这样。然而,这并不是很有趣。如果您碰巧编写了具有多个类嵌套级别的代码,那么请停止这样做。很可能你做错了什么。
最后但同样重要的是,实际上,至少我应该提到嵌套类和内部类也可以嵌套在更深的结构中。内部类不能包含嵌套类,但嵌套类可以包含内部类。为什么?我从来没有遇到过谁能可靠地告诉我真正的原因。没有架构上的原因。可能是这样的。Java 不允许这样。然而,这并不是很有趣。如果您碰巧编写了具有多个类嵌套级别的代码,那么请停止这样做。很可能你做错了什么。
# 包
......@@ -887,11 +887,11 @@ public static void main(String[] args) {
}
```
它看起来简单得多(它是),并且,如果方法没有使用任何字段,您可能认为没有理由使方法非静态。在 Java 的前 10 年中,静态方法得到了大量使用。甚至还有一个术语,实用程序类,它意味着一个类只有静态方法,不应该实例化。随着**控制反转**容器的出现,我们往往采用较少的静态方法。当使用静态方法时,使用**依赖注入**难度较大,创建测试也比较困难。我们将在接下来的几章中讨论这些高级主题。目前,您将了解静态方法是什么,哪些方法可以使用;但是,通常,除非对它们有非常特殊的需求,否则我们将避免使用它们。
它看起来简单得多(它是),并且,如果方法没有使用任何字段,您可能认为没有理由使方法非静态。在 Java 的前 10 年中,静态方法得到了大量使用。甚至还有一个术语,工具类,它意味着一个类只有静态方法,不应该实例化。随着**控制反转**容器的出现,我们往往采用较少的静态方法。当使用静态方法时,使用**依赖注入**难度较大,创建测试也比较困难。我们将在接下来的几章中讨论这些高级主题。目前,您将了解静态方法是什么,哪些方法可以使用;但是,通常,除非对它们有非常特殊的需求,否则我们将避免使用它们。
稍后,我们将研究如何在层次结构中实现类,以及类如何实现接口和扩展其他类。当我们查看这些特性时,我们将看到,有所谓的抽象类可能包含抽象方法。这些方法有`abstract`修饰符,它们不仅定义名称、参数类型(和名称)以及返回类型。扩展抽象类的具体(非抽象)类应该定义它们。
抽象方法的对立面是用`final`修饰符声明的最终方法。`final`方法不能在子类中重写
抽象方法的对立面是用`final`修饰符声明的最终方法。`final`方法不能在子类中覆盖
# 接口
......@@ -911,13 +911,13 @@ public static void main(String[] args) {
# 参数传递
在 Java 中,参数是按值传递的。当方法修改参数变量时,只修改原始值的副本。在方法调用期间复制任何基元值。当对象作为参数传递时,则传递对该对象的引用的副本。
在 Java 中,参数是按值传递的。当方法修改参数变量时,只修改原始值的副本。在方法调用期间复制任何原始值。当对象作为参数传递时,则传递对该对象的引用的副本。
这样,就可以为方法修改对象。对于具有其原始对应项的类,以及对于`String`和其他一些类类型,对象只是不提供方法或字段来修改状态。这对于语言的完整性很重要,并且在对象和原始类型值自动转换时不会遇到麻烦。
在其他情况下,当对象是可修改的时,该方法可以有效地处理传递给它的对象。这也是我们示例中的`sort()`方法在数组上的工作方式。同一个数组本身也是一个对象,会被修改。
这种论点的传递比其他语言要简单得多。其他语言允许开发人员混合传递引用和**传递值**参数。在 Java 中,当您单独使用一个变量作为表达式将一个参数传递给一个方法时,您可以确保变量本身不会被修改。但是,如果对象是可变的,则可以修改它。
这种参数的传递比其他语言要简单得多。其他语言允许开发人员混合传递引用和**传递值**参数。在 Java 中,当您单独使用一个变量作为表达式将一个参数传递给一个方法时,您可以确保变量本身不会被修改。但是,如果对象是可变的,则可以修改它。
一个对象是可变的,如果它可以被修改,直接或通过一些方法调用改变它的一些字段的值。当一个类被设计成在对象创建之后没有正常的方式来修改对象的状态时,对象是不可变的。类`Byte``Short``Integer``Long``Float``Double``Boolean``Character`以及`String`在 JDK 中被设计成对象是不可变的。使用反射可以克服某些类的不变性实现的限制,但这样做是黑客行为,而不是专业的编码。这样做的目的只有一个,即更好地了解和理解一些 Java 类的内部工作原理,而不是别的。
......@@ -927,7 +927,7 @@ public static void main(String[] args) {
静态字段属于该类。这意味着类的所有实例都共享其中一个。正常的、非静态的字段属于对象。如果您有一个名为`f`的字段,那么类的每个实例都有自己的`f`。如果将`f`声明为`static`,则实例将共享同一`f`字段。
`final`字段初始化后不能修改。初始化可以在声明它们的行、初始化程序块或构造器代码中完成。严格的要求是初始化必须在构造器返回之前发生。这样,`final`关键字的含义就与类或方法的含义大不相同了。在扩展类中,`final`类不能被扩展,`final`方法不能被重写,我们将在下一章中看到。`final`字段要么未初始化,要么在实例创建期间获取值。编译器还检查代码是否在创建对象实例期间或类加载期间初始化了所有的`final`字段(如果`final`字段是`static`),以及代码是否没有访问/读取任何尚未初始化的`final`字段。
`final`字段初始化后不能修改。初始化可以在声明它们的行、初始化程序块或构造器代码中完成。严格的要求是初始化必须在构造器返回之前发生。这样,`final`关键字的含义就与类或方法的含义大不相同了。在扩展类中,`final`类不能被扩展,`final`方法不能被覆盖,我们将在下一章中看到。`final`字段要么未初始化,要么在实例创建期间获取值。编译器还检查代码是否在创建对象实例期间或类加载期间初始化了所有的`final`字段(如果`final`字段是`static`),以及代码是否没有访问/读取任何尚未初始化的`final`字段。
一个常见的误解是,`final`字段必须在声明时初始化。它可以在初始化器代码或构造器中完成。限制条件是,如果有更多的构造器,无论调用哪个构造器,`final`字段都必须初始化一次。
......
......@@ -25,7 +25,7 @@
在上一章中,我们实现了一个简单的排序算法。代码可以对`String`数组的元素进行排序。我们这样做是为了学习。在实际应用中,JDK 中有一个现成的排序解决方案,可以对`Collection`对象中可比较的成员进行排序。
JDK 包含一个名为`Collections`实用程序类,它本身包含一个静态方法`Collections.sort`。此方法可以对具有成员为`Comparable`的任何`List`进行排序(更准确地说,成员是实现`Comparable`接口的类的实例)。`List``Comparable`是在 JDK 中定义的接口。因此,如果我们要对`Strings`列表进行排序,最简单的解决方案如下:
JDK 包含一个名为`Collections`工具类,它本身包含一个静态方法`Collections.sort`。此方法可以对具有成员为`Comparable`的任何`List`进行排序(更准确地说,成员是实现`Comparable`接口的类的实例)。`List``Comparable`是在 JDK 中定义的接口。因此,如果我们要对`Strings`列表进行排序,最简单的解决方案如下:
```java
public class SimplestStringListSortTest {
......@@ -44,13 +44,13 @@ public class SimplestStringListSortTest {
}
```
这个代码片段来自一个示例 JUnit 测试,这就是我们在方法前面有`@Test`的原因。我们稍后将详细讨论。要执行该测试,我们可以发出以下命令:
这个代码片段来自一个示例 JUnit 测试,这就是我们在方法前面有`@Test`的原因。我们稍后将详细讨论。要执行该测试,我们可以发出以下命令:
```java
$ mvn -Dtest=SimplestStringListSortTest test
```
然而,这种实现并不能满足我们的需要。主要原因是我们想学些新东西。使用 JDK 的`sort()`方法并没有教给您任何新的东西,除了该方法前面的`@Test`
然而,这种实现并不能满足我们的需要。主要原因是我们想学些新东西。使用 JDK 的`sort()`方法并没有教给您任何新的东西,除了该方法前面的`@Test`
如果在前面的代码中有一些您无法理解的内容,那么您可以在本书中翻回一些页面,并查阅 JDK 的 [Oracle 在线文档](https://docs.oracle.com/javase/9/docs/api/),但仅此而已。你已经知道这些事情了。
......@@ -68,7 +68,7 @@ $ mvn -Dtest=SimplestStringListSortTest test
但是,如果我们想要对包含`Student`对象的列表进行排序,`Student`类必须实现`Comparable`接口。但是等等!你如何比较两个学生?按姓名、年龄或平均分数?
把一个学生和另一个学生作比较并不是这门课的基本特征。每个类或包、库或编程单元都应该有一个职责,它应该只实现这个职责,而不实现其他职责。这并不确切。这不是数学。有时,很难判断一个特性是否适合这个职责。可比性可能是某些数据类型的固有特征,例如`Integer``Double`。其他类没有这种固有的比较特性。
把一个学生和另一个学生作比较并不是这个类的基本特征。每个类或包、库或编程单元都应该有一个职责,它应该只实现这个职责,而不实现其他职责。这并不确切。这不是数学。有时,很难判断一个特性是否适合这个职责。可比性可能是某些数据类型的固有特征,例如`Integer``Double`。其他类没有这种固有的比较特性。
有一些简单的技术可以确定特性是否应该是类的一部分。例如,对于一个学生,你可以问真人他们的名字和年龄,他们也可以告诉你他们的平均分。如果你让他们中的一个去`compareTo`(另一个学生),因为`Comparable`接口需要这个方法,他们很可能会问,“用什么属性或者怎么做?”如果他们不是有礼貌的类型,他们可以简单地回答“什么?”(更不用说缩写 WTF,它代表一周的最后三个工作日,在这种情况下很流行。)在这种情况下,您可能会怀疑实现该特性可能不在该类及其关注的领域;比较应该与原始类的实现分离开来。这也称为**关注点分离**,与 SRP 密切相关。
......@@ -84,7 +84,7 @@ JDK 类`java.util.Arrays`中还有`sort`方法。它们对数组排序或仅对
请注意,本节只是让您体验一下算法的复杂性。这是远远不够精确,我在徒劳的希望,没有数学家阅读这一点,并把诅咒我。有些解释含糊不清。如果你想深入学习计算机科学,那么在读完这本书之后,找一些其他的书或者访问在线课程。
当我们讨论一般排序问题时,我们考虑的是一些对象的一般有序集合,其中任意两个对象可以在排序时进行比较和交换。我们还假设这是一种地排序。这意味着我们不会创建另一个列表或数组来按排序顺序收集原始对象。当我们谈论算法的速度时,我们谈论的是一些抽象的东西,而不是毫秒。当我们想谈论毫秒时,实际的持续时间,我们应该已经有了一个在真实计算机上运行的编程语言的实现。
当我们讨论一般排序问题时,我们考虑的是一些对象的一般有序集合,其中任意两个对象可以在排序时进行比较和交换。我们还假设这是一种地排序。这意味着我们不会创建另一个列表或数组来按排序顺序收集原始对象。当我们谈论算法的速度时,我们谈论的是一些抽象的东西,而不是毫秒。当我们想谈论毫秒时,实际的持续时间,我们应该已经有了一个在真实计算机上运行的编程语言的实现。
没有实现的抽象形式的算法不会这样做。不过,一个算法的时间和内存需求还是值得讨论的。当我们这样做的时候,我们通常会研究算法对于大量数据的行为。对于一小部分数据,大多数算法都很快。排序两个数字通常不是问题,是吗?
......@@ -334,7 +334,7 @@ public interface SortSupport {
}
```
`@Override`释向 Java 编译器发出信号,表示该方法正在重写父类的方法,或者在本例中重写接口的方法。方法可以重写没有此注释的父方法;但是,如果使用注释,如果方法没有重写,编译将失败。这有助于您在编译时发现父类或接口中发生了更改,而我们在实现中没有遵循该更改,或者我们只是犯了一个错误,认为我们将重写一个方法,而实际上我们没有这样做。由于注释在单元测试中大量使用,我们将在后面更详细地讨论注释。
`@Override`解向 Java 编译器发出信号,表示该方法正在覆盖父类的方法,或者在本例中覆盖接口的方法。方法可以覆盖没有此注释的父方法;但是,如果使用注释,如果方法没有覆盖,编译将失败。这有助于您在编译时发现父类或接口中发生了更改,而我们在实现中没有遵循该更改,或者我们只是犯了一个错误,认为我们将覆盖一个方法,而实际上我们没有这样做。由于注释在单元测试中大量使用,我们将在后面更详细地讨论注释。
这也意味着我们需要两个新接口-`Swapper``Comparator`。我们很幸运,Java 运行时已经定义了一个正好符合目的的`Comparator`接口。您可能已经从下面的`import`语句中猜到了:
......@@ -365,7 +365,7 @@ public interface Sortable {
# 架构考虑
我们创建了一个接口和一个简单的实现。在实现过程中,我们发现该接口需要支持该算法的其他接口和方法。这通常发生在代码的架构设计期间,在实现之前。出于说教的原因,我在开发代码时遵循了接口的构建。在现实生活中,当我创建接口时,我一步就创建了它们,因为我有足够的经验。我在 1983 年左右用 FORTRAN 编写了第一个快速排序代码。然而,这并不意味着我只是用任何问题来击中靶心,并给出最终的解决方案。碰巧这类问题太有名了。如果在开发过程中需要修改接口或设计的其他方面,请不要感到尴尬。这是一个自然的结果,也是一个证明,随着时间的推移,你对事物的理解会越来越好。如果架构需要更改,那么最好是这样做,而且越快越好。在实际的企业环境中,我们设计界面只是为了在开发过程中了解一些我们忘记的方面。它们的操作比排序集合要复杂一些。
我们创建了一个接口和一个简单的实现。在实现过程中,我们发现该接口需要支持该算法的其他接口和方法。这通常发生在代码的架构设计期间,在实现之前。出于说教的原因,我在开发代码时遵循了接口的构建。在现实生活中,当我创建接口时,我一步就创建了它们,因为我有足够的经验。我在 1983 年左右用 FORTRAN 编写了第一个快速排序代码。然而,这并不意味着我只是用任何问题来击中靶心,并给出最终的解决方案。碰巧这类问题太有名了。如果在开发过程中需要修改接口或设计的其他方面,请不要感到尴尬。这是一个自然的结果,也是一个证明,随着时间的推移,你对事物的理解会越来越好。如果架构需要更改,那么最好是这样做,而且越快越好。在实际的企业环境中,我们设计接口只是为了在开发过程中了解一些我们忘记的方面。它们的操作比排序集合要复杂一些。
在排序问题的例子中,我们抽象了我们想要排序到最可能的极限的东西。Java 内置的排序可以对数组或列表进行排序。如果要对不是列表或数组的对象进行排序,则必须创建一个类来实现`java.util.List`接口,该接口包含 24 个以上的方法,这些方法用于包装可排序对象,使其可以通过 JDK 排序。24 种方法似乎有很多,只是为了让我们的*变得有点*可分性。老实说,这并不是太多,在一个真实的项目中,我会把它作为一个选择。
......@@ -421,7 +421,7 @@ Maven 支持在项目生命周期中编译和执行 JUnit 测试。如果我们
测试类与生产类分开。他们进入`src/test/java`目录。当我们有一个名为`BubbleSort`的类时,那么测试将被命名为`BubbleSortTest`。此约定有助于执行环境将测试与不包含测试但执行测试所需的类分开。为了测试我们刚刚创建的排序实现,我们可以提供一个类,该类目前只包含一个`canSortStrings`方法。
单元测试方法名称用于记录正在测试的功能。由于 JUnit 框架调用每个具有`@Test`的方法,因此测试的名称在我们的代码中不会被引用。我们可以大胆地使用任意长的方法名;它不会妨碍调用方法的地方的可读性:
单元测试方法名称用于记录正在测试的功能。由于 JUnit 框架调用每个具有`@Test`的方法,因此测试的名称在我们的代码中不会被引用。我们可以大胆地使用任意长的方法名;它不会妨碍调用方法的地方的可读性:
```java
package packt.java189fundamentals.ch03.main.bubble.simple;
......@@ -492,7 +492,7 @@ sort.setSwapper(new SwapActualNamesArrayElements());
sort.sort(names);
```
测试的最后但最重要的部分是断言结果是我们期望的结果。JUnit 在`Assert`课程的帮助下帮助我们做到这一点:
测试的最后但最重要的部分是断言结果是我们期望的结果。JUnit 在`Assert`的帮助下帮助我们做到这一点:
```java
Assert.assertEquals(List.of(
......@@ -594,7 +594,7 @@ public class ArrayListSortable implements Sortable {
这个类封装了`ArrayList`,然后实现了`gets``size`方法对`ArrayList`的访问。`ArrayList`本身声明为`final`。回想一下,`final`字段必须在构造器完成时定义。这保证了当我们开始使用对象时字段就在那里,并且在对象生存期内它不会改变。然而,注意,对象的内容,在这种情况下,`ArrayList`的元素可以改变。如果不是这样的话,我们就无法整理它。
下一节课`StringComparator`。这非常简单,我不在这里列出它;我将把它留给您来实现可以比较两个`Strings``java.util.Comparator`接口。这应该不难,特别是因为这个类已经是以前版本的`BubbleSortTest`类的一部分(提示这是一个匿名类,我们存储在名为`stringCompare`的变量中)。
下一个类`StringComparator`。这非常简单,我不在这里列出它;我将把它留给您来实现可以比较两个`Strings``java.util.Comparator`接口。这应该不难,特别是因为这个类已经是以前版本的`BubbleSortTest`类的一部分(提示这是一个匿名类,我们存储在名为`stringCompare`的变量中)。
我们还必须实现`ArrayListSwapper`,这也不应该是一个很大的惊喜:
......@@ -669,7 +669,7 @@ public void canNotSortMixedElements() {
当程序使用`throw`命令或 Java 运行时抛出异常时,程序的执行将在该点停止,而不是执行下一个命令,而是在捕获异常的地方继续。它可以在同一个方法中,也可以在调用链中的某个调用方法中。要捕获异常,抛出异常的代码应该在一个`try`块中,`try`块后面的`catch`语句应该指定一个与抛出的异常兼容的异常。
如果没有捕获到异常,那么 Java 运行时将打印出异常消息以及栈跟踪,该跟踪将包含异常发生时调用栈上的所有类、方法和行号。在我们的例子中,如果我们移除`@Test``(expected = ClassCastException.class)`参数,测试执行将在输出中产生以下跟踪:
如果没有捕获到异常,那么 Java 运行时将打印出异常消息以及栈跟踪,该跟踪将包含异常发生时调用栈上的所有类、方法和行号。在我们的例子中,如果我们移除`@Test``(expected = ClassCastException.class)`参数,测试执行将在输出中产生以下跟踪:
```java
packt.java189fundamentals.ch03.main.bubble.simple.NonStringElementInCollectionException: There are mixed elements in the collection.
......@@ -1345,7 +1345,7 @@ public class QuickSort<E> extends AbstractSort<E> {
我们已经看到 Java 中有四种访问级别。当类内部没有提供修饰符时,方法或字段可以是`private``protected``public``default`(也称为包私有)。当您开发一个用于多个项目的复杂库时,库本身将在许多包中包含许多类。当然会有一些类和方法,这些类和方法中的字段应该只在库中由来自不同包的其他类使用。这些类不能被库外的代码使用。使它们比`public`更不可见会使它们在库中无法使用。制造它们`public`将使它们从外面可见。这不好。
在我们的代码中,编译成 JAR 的 Maven 模块`quick`只有在`sort`方法可以调用`qsort`的情况下才能使用。但是,我们不希望`qsort`直接从外部使用。在下一个版本中,我们可能希望开发一个使用来自`NonRecursiveQuickSort`类的`qsort`的版本,我们不希望客户抱怨他们的代码由于库的小升级而无法编译或工作。我们可以证明,内部方法和类是公共的,它们不是用来使用的,而是徒劳的。使用我们库的开发人员不阅读文档。这也是为什么我们不写过多的评论。没有人会读它,甚至执行代码的处理器也不会。
在我们的代码中,编译成 JAR 的 Maven 模块`quick`只有在`sort`方法可以调用`qsort`的情况下才能使用。但是,我们不希望`qsort`直接从外部使用。在下一个版本中,我们可能希望开发一个使用来自`NonRecursiveQuickSort`类的`qsort`的版本,我们不希望客户抱怨他们的代码由于库的小升级而无法编译或工作。我们可以证明,内部方法和类是公共的,它们不是用来使用的,而是徒劳的。使用我们库的开发人员不阅读文档。这也是为什么我们不写过多的注释。没有人会读它,甚至执行代码的处理器也不会。
# 什么是 Java 模块?
......
......@@ -55,7 +55,7 @@ RGBY 4/0
当我们用面向对象的思想开发一段代码时,我们会尝试对真实世界建模,并将真实世界的对象映射到程序中的对象。你肯定听过面向对象的解释,用非常典型的几何物体的例子,或者用汽车和马达的东西来解释组成。就我个人而言,我认为这些例子太简单了,无法得到很好的理解。他们可能是好的开始,但我们已经在这本书的第四章。策划者的游戏好多了。它比矩形和三角形要复杂一些,但没有电信计费应用或原子能发电厂控制那么复杂。
在这个游戏中,我们有哪些真实世界的物体?我们有一张桌子,我们有不同颜色的别针。我们当然需要两个 Java 类。桌子里有什么?每行有四个位置。也许我们需要一节课。表将有行。我们还需要一些隐藏秘密的东西。这也可以是一行,并且每行还可以保存关于有多少位置和多少颜色匹配的信息。在秘密行的情况下,这个信息是明显的 -4 和 0。
在这个游戏中,我们有哪些真实世界的物体?我们有一张桌子,我们有不同颜色的别针。我们当然需要两个 Java 类。桌子里有什么?每行有四个位置。也许我们需要一个类。表将有行。我们还需要一些隐藏秘密的东西。这也可以是一行,并且每行还可以保存关于有多少位置和多少颜色匹配的信息。在秘密行的情况下,这个信息是明显的 -4 和 0。
什么是别针?每个别针都有一种颜色,通常就是它。除了可以插入桌子上的孔之外,没有其他的销钉的特性,但这是我们不会建模的真实特性。基本上,别针是一种颜色,而不是别的。这样,我们可以在早期就从模型中消除别针类,甚至在我们用 Java 创建别针类之前。相反,我们有颜色。
......@@ -243,7 +243,7 @@ JDK 中没有直接实现`Collection`接口。类实现了`Collection`的一个
在此操作期间,无法可靠地使用哈希表,这可能是多线程环境中的一些问题源。在单线程代码中,您不会遇到这个问题。当您调用`add()`方法时,哈希表(集合或映射)决定必须调整表的大小。`add()`方法调用调整大小的方法,直到完成后才返回。单线程代码在此期间不可能使用哈希表—单线程正在执行调整大小。在多线程环境中,可能会发生这样的情况:一个线程调用开始调整大小的`add()`,而另一个线程也在重新组织哈希表时调用`add()`。在这种情况下,JDK 中的哈希表实现将抛出`ConcurrentModificationException`
`HashSet``HashMap`使用集合中存储的`Object`提供的哈希函数。`Object`类实现了`hashCode()``equals()`方法。你可以重写它们,如果你这样做了,你应该以一致的方式重写它们。首先,我们将看到它们是什么,然后如何一致地覆盖它们。
`HashSet``HashMap`使用集合中存储的`Object`提供的哈希函数。`Object`类实现了`hashCode()``equals()`方法。你可以覆盖它们,如果你这样做了,你应该以一致的方式覆盖它们。首先,我们将看到它们是什么,然后如何一致地覆盖它们。
# `equals()`方法
......@@ -295,7 +295,7 @@ public boolean equals(Object o) {
首先,该方法检查对象标识。一个`Object`总是`equals()`本身。如果作为参数传递的引用是`null`而不是对象,或者它们属于不同的类,那么这个生成的方法将返回`false`。在其他情况下,`Objects`类的静态方法(注意复数形式)将用于比较每个字段。
`Objects`实用程序类是在 Java7 中引入的。静态方法`equals()``hash()`支持`Object equals``hashCode()`方法的重写`hashCode()`在 Java7 之前的创建是相当复杂的,需要用一些幻数实现模运算,这些幻数很难解释,仅仅看代码而不知道背后的数学。
`Objects`工具类是在 Java7 中引入的。静态方法`equals()``hash()`支持`Object equals``hashCode()`方法的覆盖`hashCode()`在 Java7 之前的创建是相当复杂的,需要用一些幻数实现模运算,这些幻数很难解释,仅仅看代码而不知道背后的数学。
这种复杂性现在隐藏在以下`Objects.hash`方法背后:
......@@ -380,7 +380,7 @@ public boolean equals(Object o) {
接口定义了很多方法。两种最重要的方法是`put()``get()``put(key,value)`方法可用于在地图中存储密钥/值对。如果有一对有一个键,我们想在对中设置的键`equals()`,那么旧值将被替换。此时,`put()`的返回值为旧对象,否则返回`null`。注意,返回的`null`值也可能表示与该键相关的值为`null`
`get(key)`方法返回用指定键存储的值。同样,方法`equals()`用于检查所提供的密钥与地图中使用的密钥是否相等。如果映射没有任何与作为参数提供的键相关联的值,则此方法返回`null`。这也可能意味着与键相关联的实际值是`null`参考
`get(key)`方法返回用指定键存储的值。同样,方法`equals()`用于检查所提供的密钥与地图中使用的密钥是否相等。如果映射没有任何与作为参数提供的键相关联的值,则此方法返回`null`。这也可能意味着与键相关联的实际值是`null`引用
为了区分给定键没有存储值和存储值为`null`的两种情况,有另一种方法称为`contains()`。如果此映射包含指定键的映射,则此方法返回`true`
......@@ -583,9 +583,9 @@ private boolean guessMatch(Color[] guess) {
![](img/702fd60b-15cf-4d7c-a4fd-dd4b2b6add2d.png)
关注方法、类和接口的作用以及如何使用 JavaDoc。不要解释它是如何在内部工作的。JavaDoc 不是解释算法或编码的地方。它的目的是帮助使用代码。然而,如果有人碰巧解释了一个方法是如何工作的,那就不是灾难了。评论很容易被删除。
关注方法、类和接口的作用以及如何使用 JavaDoc。不要解释它是如何在内部工作的。JavaDoc 不是解释算法或编码的地方。它的目的是帮助使用代码。然而,如果有人碰巧解释了一个方法是如何工作的,那就不是灾难了。注释很容易被删除。
然而,有一条评论比什么都没有更糟糕:过时的文档不再有效。当元素的约定发生了更改,但文档没有遵循更改,并且误导了希望调用方法、接口或类的用户时,它将面临严重的错误,并且将不知所措。
然而,有一条注释比什么都没有更糟糕:过时的文档不再有效。当元素的约定发生了更改,但文档没有遵循更改,并且误导了希望调用方法、接口或类的用户时,它将面临严重的错误,并且将不知所措。
从现在起,JavaDoc 注释将不会以打印的形式列出以保存树,电子版也不会列出,但它们在存储库中,可以检查。
......@@ -812,7 +812,7 @@ abstract protected void setFirstGuess();
如果隐藏行不允许包含任何颜色,则第一个猜测不应包含重复的颜色,因此此类中的方法是抽象的。
下一个方法是在具体类中重写的内部方法:
下一个方法是在具体类中覆盖的内部方法:
```java
protected Color[] nextGuess() {
......@@ -963,7 +963,7 @@ protected Color[] nextGuess() {
}
```
重写`nextGuess()`方法很简单。它要求超类的`nextGuess()`实现进行猜测,但丢弃了它不喜欢的猜测。
覆盖`nextGuess()`方法很简单。它要求超类的`nextGuess()`实现进行猜测,但丢弃了它不喜欢的猜测。
# `GeneralGuesser`
......@@ -1050,7 +1050,7 @@ public class Game {
这段代码有两个目的。一方面,我们希望看到代码运行并执行整个游戏。如果比赛结束了,那就没事了。这是一个非常弱的断言,而真正的集成测试执行很多断言(尽管一个测试只测试一个断言)。我们将集中在另一个目标,提供一些乐趣和可视化的游戏控制台上的文本格式,使读者不会感到无聊。
为此,我们将创建一个实用程序类,该类打印出一种颜色,并动态地将字母分配给`Color`实例。
为此,我们将创建一个工具类,该类打印出一种颜色,并动态地将字母分配给`Color`实例。
警告:这个类中有几个限制,我们必须在查看代码后讨论。我想说这段代码在这里只是为了演示*不要做*什么,为下一章建立一些推理,以及为什么我们需要重构我们在这一章中创建的代码。仔细阅读!
......@@ -1079,7 +1079,7 @@ public class PrettyPrintRow {
}
```
这是这个的核心。当一种颜色要打印时,它会得到一个指定的字母,除非它已经有了一个。由于在 JVM 中运行的每个游戏中包含分配的`Map`将使用相同的映射,因此新的`Game`被启动。它分配新的`Color`对象,很快就会用完我们在`String`常量中分配的六个字符。
这是这个的核心。当一种颜色要打印时,它会得到一个指定的字母,除非它已经有了一个。由于在 JVM 中运行的每个游戏中包含分配的`Map`将使用相同的映射,因此新的`Game`被启动。它分配新的`Color`对象,很快就会用完我们在`String`常量中分配的六个字符。
如果`Game`实例并行运行,那么我们的麻烦就更大了。这个类根本不是线程安全的。如果两个线程同时调用同一个`Color`实例的`colorToChar`方法(这不太可能,因为每个`Game`都使用自己的颜色,但请注意,编程中的**不太可能**非常像墓碑上有名的最后一句话),那么两个线程可能都会看到此时没有为颜色分配字母同时,两者都会指定字母(相同的字母或两个不同的字母,取决于运气)并增加计数器一到两次。至少,我们可以说,执行是不确定的。
......
......@@ -117,7 +117,7 @@ Color[] guess = super.nextGuess();
重构是一个巨大的主题,在这样的活动中可以遵循许多技术。[它是如此的复杂以至于有一整本马丁·福勒的书](http://martinfowler.com/books/refactoring.html),很快就会有第二版。
在我们的例子中,我们希望对代码进行的修改是实现一个并行算法。首先要修改的是`ColorManager`。当我们想在终端上打印猜测和行时,我们使用了一些糟糕的技巧来实现它。为什么没有可以打印的颜色实现?我们可以有一个扩展原始`Color`类的类,并有一个返回表示该颜色的内容的方法。你有那种方法的候选名称吗?这是`toString()`方法。它在`Object`类中实现,任何类都可以自由重写它。当您将一个对象连接到一个字符串时,自动类型转换将调用此方法将该对象转换为`String`。顺便说一下,使用`""+object`而不是`object.toString()`来避免`null`指针异常是一个老把戏。不用说,我们不使用诡计。
在我们的例子中,我们希望对代码进行的修改是实现一个并行算法。首先要修改的是`ColorManager`。当我们想在终端上打印猜测和行时,我们使用了一些糟糕的技巧来实现它。为什么没有可以打印的颜色实现?我们可以有一个扩展原始`Color`类的类,并有一个返回表示该颜色的内容的方法。你有那种方法的候选名称吗?这是`toString()`方法。它在`Object`类中实现,任何类都可以自由覆盖它。当您将一个对象连接到一个字符串时,自动类型转换将调用此方法将该对象转换为`String`。顺便说一下,使用`""+object`而不是`object.toString()`来避免`null`指针异常是一个老把戏。不用说,我们不使用诡计。
当调试器想要显示某个对象的值时,`toString()`方法也会被 IDE 调用,因此如果没有其他原因,那么为了便于开发,通常建议实现`toString()`。如果我们有一个实现了`toString()``Color`类,那么`PrettyPrintRow`类就变得相当简单,欺骗性更小:
......@@ -410,7 +410,7 @@ public boolean isUnique() {
如您所见,缓存可能会变得复杂。要专业地做到这一点,最好使用一些现成的缓存实现。我们在这里使用的缓存只是冰山一角。或者,它甚至只是在它身上瞥见的阳光。
其余的课程都相当标准,我们已经详细讨论了一些内容——对您的知识的一个很好的检查就是理解`equals()``hashCode()``toString()`方法是如何以这种方式实现的。我实现了`toString()`方法来帮助我进行调试,但它也被用于接下来的示例输出中。方法如下:
其余的都相当标准,我们已经详细讨论了一些内容——对您的知识的一个很好的检查就是理解`equals()``hashCode()``toString()`方法是如何以这种方式实现的。我实现了`toString()`方法来帮助我进行调试,但它也被用于接下来的示例输出中。方法如下:
```java
@Override
......@@ -491,7 +491,7 @@ Golang 的 GoRoutine 是纤程类型,这就是为什么您可以轻松地在 G
Java 中的一切(几乎)都是一个对象。如果我们想启动一个新线程,我们将需要一个对象,因此,一个代表线程的类。这个类是`java.lang.Thread`,它内置在 JDK 中。当您启动 Java 代码时,JVM 会自动创建一些`Thread`对象,并使用它们来运行它所需要的不同任务。如果您启动了 **VisualVM**,您可以选择任何 JVM 进程的线程选项卡,并查看 JVM 中的实际线程。例如,我启动的 VisualVM 有 29 个活动线程。其中一个是名为`main`的线程。这是一个开始执行`main`方法的方法(惊喜!)。`main`线程启动了大多数其他线程。当我们要编写一个多线程应用时,我们必须创建新的`Thread`对象并启动它们。最简单的方法是启动`new Thread()`,然后在线程上调用`start()`方法。它将开始一个新的`Thread`,它将立即结束,因为我们没有给它任何事情做。在 JDK 中,`Thread`类不执行我们的业务逻辑。以下是指定业务逻辑的两种方法:
* 创建实现`Runnable`接口的类并将其实例传递给`Thread`对象
* 创建扩展`Thread`类并重写`run`方法的类
* 创建扩展`Thread`类并覆盖`run`方法的类
下面的代码块是一个非常简单的演示程序:
......@@ -612,11 +612,11 @@ t1.start();
# `ExecutorService`
`ExecutorService`是 JDK 中的一个接口。接口的实现可以异步执行一个`Runnable``Callable`类。接口只定义实现的 API,不要求调用是异步的。实际上,这就是为什么我们使用这样的服务。以同步方式调用`Runnable`接口的`run`方法只是调用一个方法。我们不需要特殊的课程
`ExecutorService`是 JDK 中的一个接口。接口的实现可以异步执行一个`Runnable``Callable`类。接口只定义实现的 API,不要求调用是异步的。实际上,这就是为什么我们使用这样的服务。以同步方式调用`Runnable`接口的`run`方法只是调用一个方法。我们不需要特殊的
`Runnable`接口定义了一个`run`方法。它没有参数,不返回值,也不引发异常。`Callable`接口是参数化的,它定义的唯一方法`call`没有参数,但返回泛型值,还可能抛出`Exception`。在代码中,如果我们只想运行某个东西,我们就实现了`Runnable`,如果我们想返回某个东西,我们就实现了`Callable`。这两个接口都是函数式接口;因此,它们是使用 Lambda 实现的很好的候选接口。
为了获得一个`ExecutorService`实现的实例,我们可以使用实用类`Executors`。通常,当 JDK 中有一个`XYZ`接口时,可以有一个`XYZs`(复数)实用程序类,为接口的实现提供工厂。如果我们想多次启动`t1`任务,我们可以不创建新的`Thread`就这样做。我们应该使用以下执行器服务:
为了获得一个`ExecutorService`实现的实例,我们可以使用实用类`Executors`。通常,当 JDK 中有一个`XYZ`接口时,可以有一个`XYZs`(复数)工具类,为接口的实现提供工厂。如果我们想多次启动`t1`任务,我们可以不创建新的`Thread`就这样做。我们应该使用以下执行器服务:
```java
public class ThreadIntermingling {
......@@ -656,7 +656,7 @@ public class ThreadIntermingling {
当我们将任务提交给服务时,即使任务当前无法执行,它也会返回。这些任务被放入队列中,一旦有足够的资源启动它们,它们就会立即开始执行。`submit`方法返回一个`Future<?>`对象,正如我们在前面的示例中看到的那样。
它就像一张服务票。你把车交给修理工,然后你就得到了一张罚单。在汽车修好之前,你不需要呆在那里,但是,你可以随时询问汽车是否准备好了。你只需要一张票。你也可以决定等到车子准备好。物体是类似的东西。你没有得到你需要的值。它将异步计算。然而,有一个`Future`承诺它会在那里,而您访问所需对象的票证就是`Future`对象。
它就像一张服务票。你把车交给修理工,然后你就得到了一张罚单。在汽车修好之前,你不需要呆在那里,但是,你可以随时询问汽车是否准备好了。你只需要一张票。你也可以决定等到车子准备好。物体是类似的东西。你没有得到你需要的值。它将异步计算。然而,有一个`Future`承诺它会在那里,而您访问所需对象的票证就是`Future`对象。
当您有一个`Future`对象时,您可以调用`isDone()`方法来查看它是否准备就绪。您可以开始等待它在有或没有超时的情况下调用`get()`。您也可以取消执行它的任务,但是,在这种情况下,结果可能是有问题的。就像,在你的车的情况下,如果你决定取消任务,你可能会得到你的车与电机拆解。类似地,取消一个没有准备好的任务可能会导致资源丢失、数据库连接被打开和无法访问(这对我来说是一个痛苦的记忆,即使 10 年之后),或者只是一个乱七八糟的不可用对象。准备要取消的任务或不要取消它们。
......@@ -666,7 +666,7 @@ public class ThreadIntermingling {
如果线程在启动前被设置为守护进程(调用`setDaemon(true)`),那么它就是守护线程。一个自动成为启动它的守护线程的线程也是守护线程。当所有其他线程都完成并且 JVM 想要完成时,守护线程被 JVM 停止。JVM 本身执行的一些线程是守护线程,但是在应用中创建守护线程可能没有实际用途。
不关闭服务只会阻止 JVM 停止。在`main`方法完成后,代码将挂起。为了告诉`ExecutorService`不需要它拥有的线程,我们必须`shutdown`服务。呼叫只会启动关机并立即返回。在这种情况下,我们不想等待。无论如何,JVM 都会这样做。如果我们需要等待,我们将不得不调用`awaitTermination`
不关闭服务只会阻止 JVM 停止。在`main`方法完成后,代码将挂起。为了告诉`ExecutorService`不需要它拥有的线程,我们必须`shutdown`服务。调用只会启动关机并立即返回。在这种情况下,我们不想等待。无论如何,JVM 都会这样做。如果我们需要等待,我们将不得不调用`awaitTermination`
# `CompletableFuture`
......@@ -697,7 +697,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
completable future 类实现了`Future`接口,但它还提供了其他方法,当我们需要描述异步代码的执行时,它也提供了其他方便的方法。额外的方法在`CompletionStage`接口中定义,起初这个名字有点奇怪,但我们很快就会理解它的真正含义。
我们已经看到了在这个接口中定义的许多方法之一-`thenAcceptAsync()`。前面的代码创建了一个由 Lambda 表达式定义的完全未来。静态方法`supplyAsync()`接受`Supplier`作为参数。Java 的线程系统稍后会调用这个供应商。此方法的返回值是一个`CompletableFuture`,用于使用`thenAcceptAsync()`方法创建另一个`CompletableFuture`。第二个`CompletableFuture`与第一个`CompletableFuture`相连。只有当第一个完成时,它才会开始。`thenAcceptAsync()`的参数是一个消费者,它将消费`Supplier`提供的第一个`CompletableFuture`的结果。代码的结构可以用以下伪代码来描述:
我们已经看到了在这个接口中定义的许多方法之一-`thenAcceptAsync()`。前面的代码创建了一个由 Lambda 表达式定义的完全`Future`。静态方法`supplyAsync()`接受`Supplier`作为参数。Java 的线程系统稍后会调用这个供应商。此方法的返回值是一个`CompletableFuture`,用于使用`thenAcceptAsync()`方法创建另一个`CompletableFuture`。第二个`CompletableFuture`与第一个`CompletableFuture`相连。只有当第一个完成时,它才会开始。`thenAcceptAsync()`的参数是一个消费者,它将消费`Supplier`提供的第一个`CompletableFuture`的结果。代码的结构可以用以下伪代码来描述:
```java
CompletableFuture.supplyAsync( supply_value ).thenAcceptAsync( consume_the_value )
......@@ -705,16 +705,16 @@ CompletableFuture.supplyAsync( supply_value ).thenAcceptAsync( consume_the_value
它说启动由`supply_value`表示的`Supplier`,当它完成时,将这个值提供给由`consume_the_value`表示的消费者。示例代码计算 PI 的值并提供该值。`consume_the_value`部分将值打印到输出。当我们运行代码时,文本`All is scheduled`可能会首先打印到输出中,然后才打印 PI 的计算值。
类还实现了许多其他方法。当完全未来不产生任何价值或者我们不需要消耗价值时,我们应该使用`thenRunAsync(Runnable r)`方法。
类还实现了许多其他方法。当`CompletableFuture`不产生任何值或者我们不需要消耗值时,我们应该使用`thenRunAsync(Runnable r)`方法。
如果我们想消费价值,同时又想从中创造新的价值,那么我们应该使用`thenApplyAsync()`方法。此方法的参数是一个`Function`,它获取运行后`CompletableFuture`的结果,结果是`CompletableFuture thenApplyAsync()`返回的值。
如果我们想消费值,同时又想从中创造新的值,那么我们应该使用`thenApplyAsync()`方法。此方法的参数是一个`Function`,它获取运行后`CompletableFuture`的结果,结果是`CompletableFuture thenApplyAsync()`返回的值。
可完成的未来完成之后,还有许多其他方法执行代码。所有这些都用于在第一个可完成的将来完成后指定某个回调。完全未来代码的执行可能引发异常。在这种情况下,可完成的未来就完成了;它不会抛出异常。异常被捕获并存储在`CompletableFuture`对象中,只有当我们想访问调用`get()`方法的结果时才会抛出异常。方法`get()`抛出一个封装原始异常的`ExecutionException``join()`方法抛出原始异常。
`CompletableFuture`完成之后,还有许多其他方法执行代码。所有这些都用于在第一个可完成的将来完成后指定某个回调。`CompletableFuture`代码的执行可能引发异常。在这种情况下,`CompletableFuture`就完成了;它不会抛出异常。异常被捕获并存储在`CompletableFuture`对象中,只有当我们想访问调用`get()`方法的结果时才会抛出异常。方法`get()`抛出一个封装原始异常的`ExecutionException``join()`方法抛出原始异常。
`thenAcceptAsync()`这样的方法有它们的同步对,例如`thenAccept()`。如果调用此函数,则将执行传递的代码:
* 如果此代码所依赖的可完成未来尚未完成,则使用用于执行原始可完成未来的同一线程;或者
* 如果可完成的未来已经完成,则使用普通调用线程
* 如果此代码所依赖的`CompletableFuture`尚未完成,则使用用于执行原始`CompletableFuture`的同一线程;或者
* 如果`CompletableFuture`已经完成,则使用普通调用线程
换句话说,如果我们再看看伪代码:
......@@ -929,7 +929,7 @@ public class SynchronizedDemo implements Runnable {
使用者在循环中使用队列中的对象,并且只有在队列中没有可读取的内容时才调用`wait`。当生产者调用`notifyAll`时,没有消费者等待,通知被忽略。它飞走了,但这不是问题;消费者没有等待。当消费者消费了一个对象并调用了`notifyAll`,并且没有生产者等待时,情况也是一样的。这不是问题。
消费者消费,呼叫`notifyAll`,在通知悬而未决后,找不到等待的生产者,生产者就开始等待,这是不可能发生的。这不可能发生,因为整个代码都在一个`synchronized`块中,它确保没有生产者在关键部分。这就是为什么只有在获取`Object`类的锁时才能调用`wait``notify``notifyAll`的原因。
消费者消费,调用`notifyAll`,在通知悬而未决后,找不到等待的生产者,生产者就开始等待,这是不可能发生的。这不可能发生,因为整个代码都在一个`synchronized`块中,它确保没有生产者在关键部分。这就是为什么只有在获取`Object`类的锁时才能调用`wait``notify``notifyAll`的原因。
如果有许多使用者执行相同的代码,并且他们同样擅长使用对象,那么调用`notify`而不是`notifyAll`就是一种优化。在这种情况下,`notifyAll`只会唤醒所有使用者线程。然而,只有幸运的人才会意识到他们被吵醒了;其他人会看到其他人已经逃脱了诱饵。
......@@ -1207,7 +1207,7 @@ private void stopAsynchronousGuessers(IntervalGuesser[] guessers) {
它很少使用,在开始为实际商业环境执行微基准之前,我们必须三思而后行。MicroBenchmark 是一个诱人的工具,可以在不知道是否值得优化代码的情况下优化一些小东西。当我们有一个在多个服务器上运行多个模块的大型应用时,我们如何确保改进应用的某个特殊部分能够显著提高性能?它是否会回报增加的收入,产生如此多的利润,以弥补性能测试和开发中产生的成本?从统计学上讲,你几乎可以肯定,这样的优化,包括微基准,不会有回报。
我曾经维护过一位资深同事的密码。他创建了一个高度优化的代码来识别文件中存在的配置关键字。他创建了一个程序结构,它表示基于键字符串中的字符的决策树。如果配置文件中有一个关键字拼写错误,代码会在第一个字符处抛出异常,从而确定关键字不正确。要插入一个新的关键字,它需要通过代码结构来找到新关键字最初与已有关键字不同的地方,并扩展深度嵌套的`if/else`结构。阅读关键字列表处理是可能的,从评论中列出了所有的关键字,他没有忘记文件。代码运行速度惊人,可能节省了 Servlet 应用几毫秒的启动时间。应用仅在每隔几天进行一次系统维护之后才启动几个月。你呢感受一下讽刺吧?资历并不总是年数。那些更幸运的人可以拯救他们内心的孩子。
我曾经维护过一位资深同事的密码。他创建了一个高度优化的代码来识别文件中存在的配置关键字。他创建了一个程序结构,它表示基于键字符串中的字符的决策树。如果配置文件中有一个关键字拼写错误,代码会在第一个字符处抛出异常,从而确定关键字不正确。要插入一个新的关键字,它需要通过代码结构来找到新关键字最初与已有关键字不同的地方,并扩展深度嵌套的`if/else`结构。阅读关键字列表处理是可能的,从注释中列出了所有的关键字,他没有忘记文件。代码运行速度惊人,可能节省了 Servlet 应用几毫秒的启动时间。应用仅在每隔几天进行一次系统维护之后才启动几个月。你呢感受一下讽刺吧?资历并不总是年数。那些更幸运的人可以拯救他们内心的孩子。
那么,什么时候应该使用微基准呢?我可以看到两个方面:
......
......@@ -171,13 +171,13 @@ Content-Type: text/html; charset=UTF-8
自上次发布 HTTP 以来,经过近 20 年的时间,最新版本的 HTTP 于 2015 年发布。这个新版本的协议与以前的版本相比有一些增强。其中一些增强也会影响服务器应用的开发方式。
第一个也是最重要的增强是,新协议将使在单个 TCP 连接中并行发送多个资源成为可能。`Keep-Alive`标志已经可以用来避免重新创建 TCP 通道,但是当响应创建缓慢时,它没有帮助。在新协议中,其他资源也可以在同一个 TCP 通道中传递,甚至在请求得到完全服务之前。这需要协议中复杂的包处理。这对服务器应用程序员和浏览器程序员都是隐藏的。应用服务器、Servlet 容器和浏览器透明地实现了这一点。
第一个也是最重要的增强是,新协议将使在单个 TCP 连接中并行发送多个资源成为可能。`Keep-Alive`标志已经可以用来避免重新创建 TCP 通道,但是当响应创建缓慢时,它没有帮助。在新协议中,其他资源也可以在同一个 TCP 通道中传递,甚至在请求得到完全服务之前。这需要协议中复杂的包处理。这对服务器程序员和浏览器程序员都是隐藏的。应用服务器、Servlet 容器和浏览器透明地实现了这一点。
HTTP/2 将始终加密。因此,在浏览器 URL 中不可能使用`http`作为协议。永远是`https`
需要更改 Servlet 编程以利用新版本协议的优势的特性是服务器推送。Servlet 规范的 4.0 版本包括对 HTTP/2 的支持。规范可从[这个页面](https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf)获得。
服务器推送是对将来将出现的请求的 HTTP 响应。服务器如何回答一个甚至没有发出的请求?好吧,服务器已经预料到了。例如,应用发送一个 HTML 页面,其中引用了许多小图片和图标。客户端下载 HTML 页面,构建 DOM 结构,进行分析,实现所需图片,并发送图片请求。应用程序员知道那里有什么图片,甚至在浏览器请求图片之前就可以编写代码让服务器发送这些图片。每一个这种性质的响应都包含一个该响应所针对的 URL。当浏览器需要资源时,它会意识到资源已经存在,并且不会发出新的请求。在`HttpServlet`中,程序应该通过请求的新`getPushBuilder()`方法访问`PushBuilder`,并使用该方法将资源下推到客户端。
服务器推送是对将来将出现的请求的 HTTP 响应。服务器如何回答一个甚至没有发出的请求?好吧,服务器已经预料到了。例如,应用发送一个 HTML 页面,其中引用了许多小图片和图标。客户端下载 HTML 页面,构建 DOM 结构,进行分析,实现所需图片,并发送图片请求。程序员知道那里有什么图片,甚至在浏览器请求图片之前就可以编写代码让服务器发送这些图片。每一个这种性质的响应都包含一个该响应所针对的 URL。当浏览器需要资源时,它会意识到资源已经存在,并且不会发出新的请求。在`HttpServlet`中,程序应该通过请求的新`getPushBuilder()`方法访问`PushBuilder`,并使用该方法将资源下推到客户端。
# Cookie
......@@ -225,7 +225,7 @@ Servlet 是在实现 Servlet 容器环境的 Web 服务器中执行的 Java 类
JSR 代表 Java 规范请求。这些是对 Java 语言、库接口和其他组件的修改请求。这些请求经过一个评估过程,当它们被接受时,它们就成为一个标准。这个过程由 Java 社区流程(JCP)定义。JCP 也有文档记录,有不同的版本。当前版本为 2.10,可在[这个页面](https://jcp.org/en/procedures/overview)找到。
Servlet 程序实现 Servlet 接口。通常,这会受到扩展`HttpServlet`类的影响,这个类是`Servlet`接口的抽象实现。这个抽象类实现了`doGet()``doPost()``doPut()``doDelete()``doHead()``doOption()``doTrace()`等方法,可以被扩展它的实际类自由重写。如果 Servlet 类没有重写其中一个方法,则发送相应的 HTTP 方法`GET``POST`等,将返回`405 Not Allowed`状态码。
Servlet 程序实现 Servlet 接口。通常,这会受到扩展`HttpServlet`类的影响,这个类是`Servlet`接口的抽象实现。这个抽象类实现了`doGet()``doPost()``doPut()``doDelete()``doHead()``doOption()``doTrace()`等方法,可以被扩展它的实际类自由覆盖。如果 Servlet 类没有覆盖其中一个方法,则发送相应的 HTTP 方法`GET``POST`等,将返回`405 Not Allowed`状态码。
# HelloWorld Servlet
......@@ -291,7 +291,7 @@ public class HelloWorld extends HttpServlet {
}
```
当 Servlet 启动时,`init`方法被调用。当 Servlet 停止服务时,调用`destroy`方法。可以重写这些方法,以提供比构造器和其他终结可能性更细粒度的控制。一个 Servlet 对象可以多次投入使用,调用`destroy`后,Servlet 容器可以再次调用`init`,因此这个周期与对象的生命周期没有严格的联系。通常,我们在这些方法中做的并不多,但有时,您可能需要在其中编写一些代码。
当 Servlet 启动时,`init`方法被调用。当 Servlet 停止服务时,调用`destroy`方法。可以覆盖这些方法,以提供比构造器和其他终结可能性更细粒度的控制。一个 Servlet 对象可以多次投入使用,调用`destroy`后,Servlet 容器可以再次调用`init`,因此这个周期与对象的生命周期没有严格的联系。通常,我们在这些方法中做的并不多,但有时,您可能需要在其中编写一些代码。
另外,请注意,一个 Servlet 对象可以用于服务多个请求,甚至可以同时服务;因此,其中的 Servlet 类和方法应该是线程安全的。该规范要求 Servlet 容器仅使用一个 Servlet 实例,以防容器在非分布式环境中运行。如果容器在同一台机器上的多个进程中运行,每个进程执行一个 JVM,甚至在不同的机器上运行,那么可以有许多 Servlet 实例来处理请求。一般来说,Servlet 类的设计应该使它们不假设只有一个线程在执行它们,但是,同时,它们也不应该假设不同请求的实例是相同的。我们根本不知道。
......@@ -572,7 +572,7 @@ public class HtmlTools {
}
```
除了`@Inject`,其余代码都简单明了。我们将在不久的将来关注`@Inject`。我们必须关注的是代码生成的 HTML 结构。生成的页面如下所示:
除了`@Inject`,其余代码都简单明了。我们将在不久的将来关注`@Inject`。我们必须关注的是代码生成的 HTML 结构。生成的页面如下所示:
```java
<html>
......@@ -722,7 +722,7 @@ public class MastermindModule extends AbstractModule {
“绑定到带有`"nrColor"`值为 6 的`@Name`的注解`int`类”
`MastermindHandler`类包含用`@Inject`注释的字段:
`MastermindHandler`类包含用`@Inject`注释的字段:
```java
@Inject
......@@ -743,7 +743,7 @@ Guesser guesser;
此注释不是特定于 Guice 的。`@Inject``javax.inject`包的一部分,是 JDK 的标准部件。JDK 不提供**依赖注入****DI**)框架,但支持不同的框架,以便它们可以使用标准的 JDK 注释,如果 DI 框架被替换,注释可以保持不变,而不是特定于框架。
当调用注入器来创建一个`MastermindHandler`实例时,它查看类,发现它有一个`int`字段,用`@Inject``@Named("nrColors")`,并在配置中发现这样一个字段的值应该是 6。它在返回`MastermindHandler`对象之前将值注入字段。类似地,它还将值注入其他字段,如果需要创建任何要注入的对象,它也会这样做。如果这些对象中有字段,那么它们也是通过注入其他对象等方式创建的。
当调用注入器来创建一个`MastermindHandler`实例时,它查看类,发现它有一个`int`字段,用`@Inject``@Named("nrColors")`,并在配置中发现这样一个字段的值应该是 6。它在返回`MastermindHandler`对象之前将值注入字段。类似地,它还将值注入其他字段,如果需要创建任何要注入的对象,它也会这样做。如果这些对象中有字段,那么它们也是通过注入其他对象等方式创建的。
这样,DI 框架就免除了程序员创建实例的负担。这是一件相当无聊的事情,而且无论如何也不是类的核心特性。相反,它创建了所有需要有一个功能性的`MastermindHandler`的对象,并通过 Java 对象引用将它们链接在一起。这样,不同对象的依赖关系(`MastermindHandler`需要`Guesser``ColorManager``Table``ColorManager`需要`ColorFactory``Table`也需要`ColorManager`等等)就变成了一个声明,通过字段上的注解来指定。这些声明在类的代码中,是它们的正确位置。除了类本身之外,我们还能在哪里指定类需要什么才能正常运行呢?
......@@ -755,7 +755,7 @@ Guice 足够聪明,您不必指定任何需要`Table`的地方,我们都将
# `MastermindHandler`类
我们已经开始列出`MastermindHandler`类,因为这个类有一百多行,所以我不把它作为一个整体包括在这里。这门课最重要的方法是`handle`
我们已经开始列出`MastermindHandler`类,因为这个类有一百多行,所以我不把它作为一个整体包括在这里。这个类最重要的方法是`handle`
```java
public void handle(HttpServletRequest request,
......@@ -1043,7 +1043,7 @@ Java 有几种可用的日志框架,每种都有优点和缺点。`java.util.l
日志框架通常使用配置文件进行配置。配置可能会限制日志记录,关闭某些级别。在正常的操作环境中,前三级通常是开启的,`INFO``DEBUG``TRACE`在真正需要时开启。也可以只为某些记录器打开和关闭某些级别。如果我们知道错误肯定在`GameSessionSaver`类中,那么我们可以为该类打开`DEBUG`级别。
日志文件还可能包含我们没有直接编码的其他信息,打印到标准输出时会非常麻烦。通常,每条日志消息都包含创建消息的精确时间、记录器的名称,在许多情况下,还包含线程的标识符。想象一下,如果你被迫把所有这些都放到每一个论点中,你很可能很快就会写一些额外的类来做这件事。不要!它已经做了专业它是记录器框架。
日志文件还可能包含我们没有直接编码的其他信息,打印到标准输出时会非常麻烦。通常,每条日志消息都包含创建消息的精确时间、记录器的名称,在许多情况下,还包含线程的标识符。想象一下,如果你被迫把所有这些都放到每一个参数中,你很可能很快就会写一些额外的类来做这件事。不要!它已经做了专业它是记录器框架。
记录器还可以配置为将消息发送到不同的位置。登录到控制台只是一种可能性。日志框架准备将消息发送到文件、数据库、Windows 事件记录器、SysLog 服务或任何其他目标。这种灵活性,即打印哪条消息、打印哪些额外信息以及打印到哪里,是通过按照单一责任原则将记录器框架执行的不同任务分为几个类来实现的。
......@@ -1057,7 +1057,7 @@ Java 有几种可用的日志框架,每种都有优点和缺点。`java.util.l
使用`System.out.println()`将消息发送到流,并且仅在 IO 操作完成时返回。使用真实日志将信息处理到记录器,并允许记录器异步地进行日志记录,而不等待完成。
如果出现系统故障,日志信息可能会丢失,这确实是一个缺点,但考虑到这种情况很少发生以及工资绩效的另一方面,这通常不是一个严重的问题。如果磁盘已满时缺少调试日志行,导致系统在任何情况下都不可用,我们会损失什么?
如果出现系统故障,日志信息可能会丢失,这确实是一个缺点,但考虑到这种情况很少发生以及性能的另一方面,这通常不是一个严重的问题。如果磁盘已满时缺少调试日志行,导致系统在任何情况下都不可用,我们会损失什么?
当出于法律原因必须保存有关系统事务的某些日志信息以便可以审核操作和实际事务时,此审核日志记录有一个例外。在这种情况下,以事务方式保存日志信息,使日志成为事务的一部分。因为这是一种完全不同的需求类型,审计日志记录通常不使用这些框架中的任何一个来完成。
......
此差异已折叠。
......@@ -319,7 +319,7 @@ public class NeedPowercord implements ConsistencyChecker {
# 注解继承
注解,就像方法或字段一样,可以在类层次结构之间继承。如果一个注解声明被标记为`@Inherited`,那么用这个注解扩展另一个类的类可以继承它。如果子类具有注解,则可以重写注解。因为 Java 中没有多重继承,所以不能继承接口上的注解。即使继承了注解,检索特定元素注解的应用代码也可以区分继承的注解和在实体本身上声明的注解。有两种方法可以获取注解,另外两种方法可以获取在实际元素上声明的、未继承的已声明注解。
注解,就像方法或字段一样,可以在类层次结构之间继承。如果一个注解声明被标记为`@Inherited`,那么用这个注解扩展另一个类的类可以继承它。如果子类具有注解,则可以覆盖注解。因为 Java 中没有多重继承,所以不能继承接口上的注解。即使继承了注解,检索特定元素注解的应用代码也可以区分继承的注解和在实体本身上声明的注解。有两种方法可以获取注解,另外两种方法可以获取在实际元素上声明的、未继承的已声明注解。
# `@Documented`注解
......@@ -327,7 +327,7 @@ public class NeedPowercord implements ConsistencyChecker {
# JDK 注解
除了用于定义注解的注解外,JDK 中还定义了其他注解。我们已经看到了其中的一些。最常用的是`@Override`注解。当编译器看到此注解时,它会检查该方法是否确实重写了继承的方法。否则将导致一个错误,使我们免于痛苦的运行时调试。
除了用于定义注解的注解外,JDK 中还定义了其他注解。我们已经看到了其中的一些。最常用的是`@Override`注解。当编译器看到此注解时,它会检查该方法是否确实覆盖了继承的方法。否则将导致一个错误,使我们免于痛苦的运行时调试。
方法、类或其他元素的文档中的注解信号,表示不使用该元素。代码中仍然存在,因为有些用户可能仍然使用它,但是如果是依赖于包含元素的库的新开发,新开发的代码不应该使用它。注解有两个参数。一个参数是`since`,它可以有字符串值,可以传递关于方法或类的版本的过期时间或版本信息。另一个参数为`forRemoval`,如果元素在库的未来版本中不出现,则为`true`。有些方法可能会被否决,因为有更好的替代方案,但是开发人员不打算从库中删除该方法。在这种情况下,`forRemoval`可以设置为`false`
......@@ -464,7 +464,7 @@ public class Checker {
通常,一个好的框架在逻辑上工作。我不知道Spring的这个特征,但我认为这是合乎逻辑的,而且神奇地,它起作用了。如果事情是合乎逻辑的,并且只是工作的话,你不需要阅读和记住文档。不过,稍微小心一点也不会有任何危害。在我意识到这个功能是这样工作的之后,我在文档中查阅了它,以看到这确实是 Spring 的一个保证特性,而不是仅仅发生在工作中的特性,而是在未来版本中可能会发生更改而不需要注意。仅使用保证功能是非常重要的,但在我们的行业中经常被忽略。
调用`isConsistent()`方法时,首先将产品信息收集到`HashMap`中,为每个`OrderItem`分配一个`ProductInformation`实例。这是在一个单独的班级里完成的。在此之后,`ProductsCheckerCollector`收集一个或多个产品项所需的`ConsistencyChecker`实例。当我们拥有这个集合时,我们只需要调用那些用这个集合中的注解之一进行注解的检查器。我们循环着做。
调用`isConsistent()`方法时,首先将产品信息收集到`HashMap`中,为每个`OrderItem`分配一个`ProductInformation`实例。这是在一个单独的里完成的。在此之后,`ProductsCheckerCollector`收集一个或多个产品项所需的`ConsistencyChecker`实例。当我们拥有这个集合时,我们只需要调用那些用这个集合中的注解之一进行注解的检查器。我们循环着做。
在这段代码中,我们使用反射。我们循环每个检查器都有的注解。为了获取注解集合,我们调用`checker.getClass().getAnnotations()`。此调用返回对象集合。每个对象都是一些 JDK 运行时生成的类的实例,这些类实现了我们在其源文件中声明为注解的接口。但是,没有保证动态创建的类只实现我们的`@interface`,而不是其他接口。因此,要获得实际的注解类,必须调用`annotationType()`方法。
......@@ -559,13 +559,13 @@ private boolean isInconsistent(ConsistencyChecker checker, Order order) {
之后,我们尝试使用表示反射类中方法的`Method`对象来调用该类。请注意,这个`Method`对象并没有直接连接到实例。我们从类中检索该方法,因此,当我们调用它时,应该将它应该处理的对象作为第一个参数传递。这样,`x.y(z)`就变成了`method.invoke(x,z)``invoke()`的最后一个参数是作为`Object`数组传递的变量数。在大多数情况下,当我们调用一个方法时,我们知道代码中的参数,即使我们不知道方法的名称并且必须使用反射。当连参数都不知道,但作为计算的问题是可用的时,我们必须将它们作为一个`Object`数组传递。
通过反射调用方法是一个危险的调用。如果我们尝试以正常方式调用一个方法,即`private`,那么编译器将发出错误信号。如果参数或类型的数目不合适,编译器将再次给我们一个错误。如果返回值不是`boolean`,或者根本没有返回值,那么我们再次得到一个编译器错误。在反射的情况下,编译器是无知的。它不知道在代码执行时我们将调用什么方法。另一方面,`invoke()`方法在被调用时可以并且将会注意到所有这些失败。如果出现上述任何问题,那么我们将得到例外。如果`invoke()`方法本身发现它不能执行我们对它的要求,那么它将抛出`InvocationTargetException``IllegalAccessException`。如果无法将实际返回值转换为`boolean`,则得到`ClassCastException`
通过反射调用方法是一个危险的调用。如果我们尝试以正常方式调用一个方法,即`private`,那么编译器将发出错误信号。如果参数或类型的数目不合适,编译器将再次给我们一个错误。如果返回值不是`boolean`,或者根本没有返回值,那么我们再次得到一个编译器错误。在反射的情况下,编译器是无知的。它不知道在代码执行时我们将调用什么方法。另一方面,`invoke()`方法在被调用时可以并且将会注意到所有这些失败。如果出现上述任何问题,那么我们将得到异常。如果`invoke()`方法本身发现它不能执行我们对它的要求,那么它将抛出`InvocationTargetException``IllegalAccessException`。如果无法将实际返回值转换为`boolean`,则得到`ClassCastException`
关于表演魔术,这是一种自然的冲动,我们觉得要做一些非凡的东西,一些杰出的。当我们尝试一些事情,做一些有趣的事情时,这是可以的,但是当我们从事专业工作时,这绝对是不可以的。一般的程序员,如果不了解您的优秀解决方案,就会在企业环境中维护代码。他们会在修复一些 bug 或实现一些小的新特性的同时,把你精心梳理的代码变成草堆。即使你是编程界的莫扎特,他们充其量也只是无名歌手。在企业环境中,一个优秀的代码可以是一首安魂曲,包含了隐喻所包含的所有含义。
最后但同样重要的是,可悲的现实是,我们通常不是编程的莫扎特。
请注意,如果原始值的返回值是基元,那么它将通过反射转换为对象,然后我们将它转换回基元值。如果方法没有返回值,换句话说,如果它是`void`,那么反射将返回`java.lang.Void`对象。`Void`对象只是一个占位符。我们不能将它转换为任何原始类型值或任何其他类型的对象。它是必需的,因为 Java 是严格的,`invoke`必须返回一个`Object`,所以运行时需要一些它可以返回的东西。我们所能做的就是检查返回值类是否真的是`Void`
请注意,如果原始值的返回值是原始类型,那么它将通过反射转换为对象,然后我们将它转换回原始值。如果方法没有返回值,换句话说,如果它是`void`,那么反射将返回`java.lang.Void`对象。`Void`对象只是一个占位符。我们不能将它转换为任何原始类型值或任何其他类型的对象。它是必需的,因为 Java 是严格的,`invoke`必须返回一个`Object`,所以运行时需要一些它可以返回的东西。我们所能做的就是检查返回值类是否真的是`Void`
让我们继续我们的故事和解决方案。我们提交了代码,它在生产中运行了一段时间,直到一个软件供应商的新更新打破它。我们在测试环境中调试代码,发现类现在包含多个方法。我们的文档清楚地说明了他们应该只有一个`public`方法,并且他们提供了一个代码,这个代码有……嗯……我们意识到其他方法是`private`。他们是对的,根据合同他们可以有`private`方法,所以我们必须修改代码。我们替换查找唯一方法的行:
......@@ -799,7 +799,7 @@ for (ConsistencyChecker checker :checkers) {
这些接口不是相互独立定义的。如果一个方法需要`Function`,而我们有`UnaryOperator`要通过,那应该不是问题。`UnaryOperator``Function`基本相同,参数类型相同。一个可以与接受一个对象并返回一个对象的`Function`一起工作的方法,如果它们具有相同的类型,应该不会有问题。这些可以是,但不一定是,不同的。为了实现这一点,`UnaryOperator`接口扩展了`Function`,因此可以用来代替`Function`
到目前为止,我们遇到的这个类中的接口是使用泛型定义的。因为泛型类型不能是基元,所以操作基元值的接口应该单独定义。例如,`Predicate`是定义`booleantest(T t)`的接口。它是一个返回`boolean`值的函数,常用于流方法。
到目前为止,我们遇到的这个类中的接口是使用泛型定义的。因为泛型类型不能是原始类型,所以操作原始值的接口应该单独定义。例如,`Predicate`是定义`booleantest(T t)`的接口。它是一个返回`boolean`值的函数,常用于流方法。
还有一些接口,例如`BooleanSupplier``DoubleConsumer``DoubleToIntFunction`等等,它们与原始类型`boolean``double``int`一起工作。不同参数类型和返回值的可能组合的数量是无限的。。。几乎。
......@@ -885,7 +885,7 @@ return order.getItems().stream()
可以说,`Map`接口以静态方式将键映射到数据结构中的值,流方法`map()`动态地将一种值映射到另一种(或相同)类型的值。
我们已经看到可以以 Lambda 表达式的形式提供函数式接口的实例。此参数不是 Lambda 表达式。这是一个方法引用。它说`map()`方法应该调用`Map piMap`上的`get()`方法,使用实际的流元素作为参数。我们很幸运`get()`也需要一个论点,不是吗?我们也可以这样写:
我们已经看到可以以 Lambda 表达式的形式提供函数式接口的实例。此参数不是 Lambda 表达式。这是一个方法引用。它说`map()`方法应该调用`Map piMap`上的`get()`方法,使用实际的流元素作为参数。我们很幸运`get()`也需要一个参数,不是吗?我们也可以这样写:
```java
.map( orderItem ->piMap.get(orderItem))
......@@ -893,7 +893,7 @@ return order.getItems().stream()
然而,这与`piMap::get`完全相同。
这样,我们就可以引用在某个实例上工作的实例方法。在我们的示例中,实例是由`piMap`变量引用的实例。也可以参考`static`方法。在这种情况下,类的名称应该写在`::`字符前面。当我们使用来自`Objects`类的`static`方法`nonNull`时,我们很快就会看到这样一个例子(注意类名是复数形式的,它在`java.util`包中,而不是`java.lang`)。
这样,我们就可以引用在某个实例上工作的实例方法。在我们的示例中,实例是由`piMap`变量引用的实例。也可以引用`static`方法。在这种情况下,类的名称应该写在`::`字符前面。当我们使用来自`Objects`类的`static`方法`nonNull`时,我们很快就会看到这样一个例子(注意类名是复数形式的,它在`java.util`包中,而不是`java.lang`)。
也可以引用实例方法,而不给出应该调用它的引用。这可以在函数式接口方法有一个额外的第一个参数的地方使用,这个参数将用作实例。我们已经在第 3 章中使用过了,“优化专业排序代码”,当我们通过`String::compareTo`时,当期望的参数是`Comparator`时。`compareTo()`方法需要一个参数,而`Comparator`接口中的`compare()`方法需要两个参数。在这种情况下,第一个参数将用作必须调用`compare()`的实例,第二个参数将传递给`compare()`。在这种情况下,`String::compareTo`与写入 Lambda 表达式`(String a, String b) -> a.compareTo(b)`相同。
......@@ -1039,7 +1039,7 @@ public class Tuple<R, S> {
我们的代码中仍有一些类需要转换为函数式风格。这些是`Checker``CheckerHelper`类。
`Checker`类中,我们可以重写`isConsistent()`方法:
`Checker`类中,我们可以覆盖`isConsistent()`方法:
```java
public boolean isConsistent(Order order) {
......@@ -1058,7 +1058,7 @@ public boolean isConsistent(Order order) {
因为您已经学习了大多数重要的流方法,所以这里几乎没有什么新问题。我们可以提到`anyMatch()`方法,如果至少有一个元素,则返回`true`,这样传递给`anyMatch()``Predicate`参数就是`true`。它可能还需要一些住宿,这样我们就可以使用另一条流中的一条流。这很可能是一个例子,当一个流表达式过于复杂,需要使用局部变量分解成更小的片段。
最后,在离开函数样式之前,我们重写`CheckHelper`类中的`containsOneOf()`方法。这不包含新元素,将帮助您检查您对`map()``filter()``flatMap()``Collector`的了解。请注意,如我们所讨论的,如果`order`至少包含一个以字符串形式给出的订单 ID,则此方法返回`true`
最后,在离开函数样式之前,我们覆盖`CheckHelper`类中的`containsOneOf()`方法。这不包含新元素,将帮助您检查您对`map()``filter()``flatMap()``Collector`的了解。请注意,如我们所讨论的,如果`order`至少包含一个以字符串形式给出的订单 ID,则此方法返回`true`
```java
public boolean containsOneOf(String... ids) {
......@@ -1250,7 +1250,7 @@ private Reader getScriptReader(String script) throws IOException {
}
```
为了从资源文件中读取脚本,我们使用 Spring`ClassPathResource`类。脚本的名称前面有`scripts`目录,后面有`.js`扩展名。其余的是相当标准的,没有什么我们在这本书中没有看到。下一个评估脚本的方法更有趣:
为了从资源文件中读取脚本,我们使用 Spring`ClassPathResource`类。脚本的名称前面有`scripts`目录,后面有`.js`扩展名。其余的是相当标准的,没有什么我们在这本书中没有看到。下一个求值脚本的方法更有趣:
```java
private Object evalScript(String script, Order order, Reader scriptReader)
......@@ -1274,7 +1274,7 @@ private Object evalScript(String script, Order order, Reader scriptReader)
如果我们想要扩展这个类来处理 JavaScript,以及其他类型的脚本,那么就必须完成这个检查,并且脚本引擎可能应该根据文件扩展名从管理器请求,而我们在这个方法中没有访问这个文件扩展名的权限。但这是未来的发展,不是本书的一部分。
当我们有了引擎,我们必须评估脚本。这将在脚本中定义函数,以便我们以后可以调用它。为了调用它,我们需要一些对象。对于 JavaScript,引擎还实现了一个`Invocable`接口。并非所有脚本引擎都实现此接口。有些脚本没有函数或方法,也没有可调用的内容。同样,当我们希望不仅允许 JavaScript 脚本,而且还允许其他类型的脚本时,这是以后要做的事情。
当我们有了引擎,我们必须求值脚本。这将在脚本中定义函数,以便我们以后可以调用它。为了调用它,我们需要一些对象。对于 JavaScript,引擎还实现了一个`Invocable`接口。并非所有脚本引擎都实现此接口。有些脚本没有函数或方法,也没有可调用的内容。同样,当我们希望不仅允许 JavaScript 脚本,而且还允许其他类型的脚本时,这是以后要做的事情。
为了调用这个函数,我们将它的名称传递给`invokeFunction()`方法,同时传递我们想要传递的参数。在本例中,这是`order`。就 JavaScript 而言,两种语言之间的集成已经相当成熟。在我们的示例中,我们可以访问作为参数传递的 Java 对象的字段和方法,并且返回的 JavaScript`true``false`值也被神奇地转换为`Boolean`。但在有些情况下,访问并不是那么简单:
......
......@@ -184,7 +184,7 @@ JDK 中有一个现成的`Publisher`类,我们将在后面讨论。当调用`P
将订阅管理为一个抽象可以想象为一个复杂的任务,但是在反应流的情况下,它非常简单。订阅者所能做和应该做的就是设置它当前可以接收的项目数,并且可以取消订阅。
为什么`Publisher`要回调`Subscriber``onSubscribe`方法?为什么它不直接返回订阅或者抛出一些错误呢?产生这种复杂行为的原因是,调用`subscribe()`方法的可能不是`Subscriber`。就像在现实生活中一样,我可以订阅一本杂志并支付一年的费用作为圣诞礼物。(这是我写这部分书的季节)在我们的代码中,一些负责向谁通知某些数据更改的布线组件呼叫`subscribe`,而不一定是用户。`Subscriber`只负责订户应该负责的最小的事情。另一个原因是整个方法是异步的。当我们订阅某些东西时,订阅可能不会立即可用并准备就绪。可能有一些长时间运行的进程需要在订阅可用之前完成,而调用`subscribe`的调用方不需要等待进程完成。当订阅准备就绪时,它将被传递给订阅服务器,传递给真正需要它的实体。
为什么`Publisher`要回调`Subscriber``onSubscribe`方法?为什么它不直接返回订阅或者抛出一些错误呢?产生这种复杂行为的原因是,调用`subscribe()`方法的可能不是`Subscriber`。就像在现实生活中一样,我可以订阅一本杂志并支付一年的费用作为圣诞礼物。(这是我写这部分书的季节)在我们的代码中,一些负责向谁通知某些数据更改的布线组件调用`subscribe`,而不一定是用户。`Subscriber`只负责订户应该负责的最小的事情。另一个原因是整个方法是异步的。当我们订阅某些东西时,订阅可能不会立即可用并准备就绪。可能有一些长时间运行的进程需要在订阅可用之前完成,而调用`subscribe`的调用方不需要等待进程完成。当订阅准备就绪时,它将被传递给订阅服务器,传递给真正需要它的实体。
`Subscriber`接口定义了`onSubscribe()``onError()`(我们已经讨论过)、`onComplete()``onNext()`方法。
......@@ -276,7 +276,7 @@ public class InventoryItem {
请注意,例如,在 Groovy 中,我们可以简单地使用一个`Long d`变量并在闭包内更改该变量。可以说,Groovy 将 Lambda 调用为闭包。在 Java 中,我们不能这样做,因为我们可以从方法内部访问的变量应该是有效的`final`。然而,这只不过是属于闭包环境的更显式的表示法。`ClosureData d`对象是`final`,与类具有的字段相反,可以在 Lambda 中修改该字段。
本章中我们真正感兴趣的最有趣的课程`InventoryKeeper`。此类实现了`Subscriber`接口,能够使用订单维护库存:
本章中我们真正感兴趣的最有趣的`InventoryKeeper`。此类实现了`Subscriber`接口,能够使用订单维护库存:
```java
package packt.java11.mybusiness.inventory;
......@@ -330,7 +330,7 @@ public class InventoryKeeper implements Flow.Subscriber<Order> {
}
```
订阅对象后调用`onSubscribe()`方法。订阅将传递给对象,并存储在字段中。由于用户在以后的呼叫中需要这个订阅,所以当处理传入`onNext`的项目并且可以接受新的项目时,字段是存储这个对象的好地方。在这个方法中,我们还将初始请求设置为三项。实际值只是说明性的。企业环境应该能够配置这样的参数:
订阅对象后调用`onSubscribe()`方法。订阅将传递给对象,并存储在字段中。由于用户在以后的调用中需要这个订阅,所以当处理传入`onNext`的项目并且可以接受新的项目时,字段是存储这个对象的好地方。在这个方法中,我们还将初始请求设置为三项。实际值只是说明性的。企业环境应该能够配置这样的参数:
```java
private ExecutorService service =
......@@ -452,7 +452,7 @@ public static void premain(String agentArgs);
`agentArgs`参数是在命令行上作为选项传递的字符串。第二个参数`Instrumentation`提供了注册类转换器的方法,这些类转换器可以修改类字节码,并提供了在运行时要求 JVM 执行类的重新定义或重新转换的方法。
Java 应用也可以在程序启动后加载代理。在这种情况下,不能在 Java 应用的方法之前调用代理,因为此时它已经启动了。为了区分这两种情况,JVM 在这种情况下调用了`agentmain`。请注意,`premain``agentmain`是为代理调用的,而不是两者都调用。单个代理可以同时实现这两个功能,这样它就能够执行在启动时加载的、在命令行上指定的或在 JVM 启动后加载的任务。
Java 应用也可以在程序启动后加载代理。在这种情况下,不能在 Java 应用的`main`方法之前调用代理,因为此时它已经启动了。为了区分这两种情况,JVM 在这种情况下调用了`agentmain`。请注意,`premain``agentmain`是为代理调用的,而不是两者都调用。单个代理可以同时实现这两个功能,这样它就能够执行在启动时加载的、在命令行上指定的或在 JVM 启动后加载的任务。
如果使用了`agentmain`,则其参数与`premain`相同。
......@@ -529,7 +529,7 @@ void redefineClasses(ClassDefinition... definitions)
为了获得这种灵活性,应用通常提供插件 API,开发人员可以使用这些 API 来扩展应用。这就需要开发人员设置编码工具,包括 IDE、构建工具和持续集成,即专业的编程环境。当插件要解决的任务很简单时,开销就太大了。在这种情况下,脚本解决方案更方便。
脚本不是万能的。当扩展应用的脚本变得太复杂时,就意味着编写脚本的可能性太大了。然而,要从一个孩子手里夺回一个玩具是很困难的。如果用户习惯了脚本的可能性,那么如果我们发布的应用的下一个版本没有提供这种可能性,他们就不会接受。因此,评估脚本功能在我们的应用中的可能用途是非常重要的。脚本,更一般地说,我们的程序的任何功能,都不会按我们预期的方式使用。它们将被用于任何可能的用途。当涉及到滥用某个功能时,用户可能会超出想象。最好事先考虑限制脚本编写的可能性,或者限制脚本的运行时间,或者限制程序同意使用的脚本的大小。如果这些限制设置合理,并且用户理解并接受这些限制,那么除了脚本功能之外,还必须考虑插件结构。
脚本不是万能的。当扩展应用的脚本变得太复杂时,就意味着编写脚本的可能性太大了。然而,要从一个孩子手里夺回一个玩具是很困难的。如果用户习惯了脚本的可能性,那么如果我们发布的应用的下一个版本没有提供这种可能性,他们就不会接受。因此,求值脚本功能在我们的应用中的可能用途是非常重要的。脚本,更一般地说,我们的程序的任何功能,都不会按我们预期的方式使用。它们将被用于任何可能的用途。当涉及到滥用某个功能时,用户可能会超出想象。最好事先考虑限制脚本编写的可能性,或者限制脚本的运行时间,或者限制程序同意使用的脚本的大小。如果这些限制设置合理,并且用户理解并接受这些限制,那么除了脚本功能之外,还必须考虑插件结构。
应用的安全性,包括插件或脚本扩展,也非常重要。脚本或插件与核心应用运行在同一个 JVM 上。一些脚本语言在脚本周围提供了一个围栏,限制了对核心应用对象和类的访问,但这是一个例外。通常,脚本以与核心应用相同的权限运行,这样它们就可以做任何事情。因此,应该以与核心应用相同的方式信任脚本。对于应用的非特权用户,脚本安装或修改不应该是可能的。这样的操作几乎总是留给系统管理员。
......@@ -584,9 +584,9 @@ void redefineClasses(ClassDefinition... definitions)
对于注释处理器,这些方法返回的值相当稳定。注释处理器将返回它可以处理的相同源版本,并返回相同的注释集。因此,以声明的方式在源代码中定义这些值是明智的。
这可以在扩展`javax.annotation.processing.AbstractProcessor`类而不是直接实现`Processor`接口时完成。这个抽象类实现了这些方法。它们都从注释中获取信息,这样我们就可以修饰扩展抽象类的类。例如,`getSupportedAnnotationTypes()`方法查看`SupportedAnnotationTypes`并返回注释中列出的注释类型字符串数组。
这可以在扩展`javax.annotation.processing.AbstractProcessor`类而不是直接实现`Processor`接口时完成。这个抽象类实现了这些方法。它们都从注释中获取信息,这样我们就可以修饰扩展抽象类的类。例如,`getSupportedAnnotationTypes()`方法查看`SupportedAnnotationTypes`并返回注释中列出的注释类型字符串数组。
现在,这是一个有点扭曲的大脑,也可能是混乱的开始。我们正在编译时执行注释处理器。但是编译器本身是一个 Java 应用,这样,时间就是编译器内部运行的代码的运行时间。`AbstractProcessor`的代码使用反射方法将`SupportedAnnotationTypes`作为运行时注释进行访问。这里面没有魔法。JDK9 中的方法如下:
现在,这是一个有点扭曲的大脑,也可能是混乱的开始。我们正在编译时执行注释处理器。但是编译器本身是一个 Java 应用,这样,时间就是编译器内部运行的代码的运行时间。`AbstractProcessor`的代码使用反射方法将`SupportedAnnotationTypes`作为运行时注释进行访问。这里面没有魔法。JDK9 中的方法如下:
```java
public Set<String> getSupportedAnnotationTypes() {
......@@ -645,7 +645,7 @@ public boolean process(final Set<? extends TypeElement> annotations,
}
```
此方法获取两个参数。第一个是为其调用的注释集。二是圆环境。因为处理器可以被多次调用,不同的调用可能有不同的环境。每次调用都在其中,`RoundEnvironment`参数是一个对象,可以用来获取给定回合的信息。它可用于获取为其调用此注释的回合的根元素。在我们的例子中,这将是一组具有`CompileScript`的类元素。我们迭代这个集合,对于每个类,我们调用`processClass()`方法。请参见下一个代码段:
此方法获取两个参数。第一个是为其调用的注释集。二是圆环境。因为处理器可以被多次调用,不同的调用可能有不同的环境。每次调用都在其中,`RoundEnvironment`参数是一个对象,可以用来获取给定回合的信息。它可用于获取为其调用此注释的回合的根元素。在我们的例子中,这将是一组具有`CompileScript`的类元素。我们迭代这个集合,对于每个类,我们调用`processClass()`方法。请参见下一个代码段:
```java
private static void processClass(final AnnotatedConstruct element) {
......@@ -665,7 +665,7 @@ private static void processAnnotation(final AnnotationMirror mirror) {
}
```
我们的`@CompileScript`定义了两个参数。第一个值是脚本文件名,第二个值是脚本引擎名称。如果未指定第二个,则将空字符串设置为默认值。注释的每一次调用`execute()`方法:
我们的`@CompileScript`定义了两个参数。第一个值是脚本文件名,第二个值是脚本引擎名称。如果未指定第二个,则将空字符串设置为默认值。注释的每一次调用`execute()`方法:
```java
private static void execute(final String scriptFileName, final String engineName) {
......
......@@ -27,15 +27,15 @@
第 1 章“Java12 入门”从基础开始,首先解释什么是“Java”并定义其主要术语,然后继续介绍如何安装编写和运行(执行)程序所需的工具。本章还描述了基本的 Java 语言构造,并用可以立即执行的示例来说明它们。
第 2 章“面向对象编程(OOP)”介绍了面向对象编程的概念及其在 Java 中的实现。每一个概念都用具体的代码示例来演示。详细讨论了类和接口的 Java 语言结构,以及重载、重写、隐藏和使用`final`关键字,最后一节介绍了多态的威力。
第 2 章“面向对象编程(OOP)”介绍了面向对象编程的概念及其在 Java 中的实现。每一个概念都用具体的代码示例来演示。详细讨论了类和接口的 Java 语言结构,以及重载、覆盖、隐藏和使用`final`关键字,最后一节介绍了多态的威力。
第 3 章“Java 基础”向读者展示了 Java 作为一种语言的更详细的观点。它从包中的代码组织开始,描述类(接口)及其方法和属性(字段)的可访问性级别。本文详细介绍了 Java 面向对象特性的主要类型引用类型,然后列出了保留关键字和限制关键字,并讨论了它们的用法。本章最后介绍了原始类型之间的转换方法,以及从原始类型到相应引用类型的转换方法。
第 4 章“处理”向读者介绍了与异常处理相关的 Java 构造的语法以及处理(处理)异常的最佳实践。本章以可用于在生产中调试应用代码的断言语句的相关主题结束。
第 5 章、“字符串、输入/输出和文件”,讨论字符串类方法,以及来自标准库和 ApacheCommons 项目的流行字符串实用程序。下面概述了 Java 输入/输出流和`java.io`包的相关类以及`org.apache.commons.io`包的一些类。文件管理类及其方法在专门的一节中进行了描述。
第 5 章、“字符串、输入/输出和文件”,讨论字符串类方法,以及来自标准库和 ApacheCommons 项目的流行字符串工具。下面概述了 Java 输入/输出流和`java.io`包的相关类以及`org.apache.commons.io`包的一些类。文件管理类及其方法在专门的一节中进行了描述。
第 6 章、“数据结构、泛型和流行实用程序”介绍了 Java 集合框架及其三个主要接口`List``Set``Map`,包括泛型的讨论和演示。`equals()``hashCode()`方法也在 Java 集合的上下文中讨论。用于管理数组、对象和时间/日期值的实用程序类也有相应的专用部分。
第 6 章、“数据结构、泛型和流行工具”介绍了 Java 集合框架及其三个主要接口`List``Set``Map`,包括泛型的讨论和演示。`equals()``hashCode()`方法也在 Java 集合的上下文中讨论。用于管理数组、对象和时间/日期值的工具类也有相应的专用部分。
第 7 章、“Java 标准和外部库”概述了 **Java 类库****JCL**)最流行的包的功能:`java.lang``java.util``java.time``java.io``java.nio``java.sql``javax.sql``java.net java.lang.math``java.math``java.awt``javax.swing``javafx`。最流行的外部库是以`org.junit``org.mockito``org.apache.log4j``org.slf4j``org.apache.commons`包为代表的。本章帮助读者避免在已经存在此类功能并且可以直接导入和删除的情况下编写自定义代码开箱即用。
......
......@@ -20,7 +20,7 @@
* **Java 程序设计语言**:一种高级程序设计语言,允许以人类可读的格式表达意图(程序),并将其翻译成计算机可执行的二进制代码
* **Java 编译器**:一种程序,它能读取用 Java 编程语言编写的文本,并将其翻译成字节码,由 **Java 虚拟机****JVM**)解释成计算机可执行的二进制代码
* **Java 虚拟机****JVM**):一种程序,它读取已编译的 Java 程序,并将其解释为计算机可执行的二进制代码
* **Java 开发工具包****JDK**):程序(工具和实用程序)的集合,包括 Java 编译器、JVM 和支持库,允许编译和执行用 Java 语言编写的程序
* **Java 开发工具包****JDK**):程序(工具和工具)的集合,包括 Java 编译器、JVM 和支持库,允许编译和执行用 Java 语言编写的程序
下一节将引导读者完成 Java12 的 JDK 的安装以及基本的相关术语和命令
......@@ -61,11 +61,11 @@
![](img/3a7e798e-2c94-4ef5-8ef2-695f2dd2df16.png)
# 命令、工具和实用程序
# 命令、工具和工具
如果按照安装说明进行操作,您可能已经注意到目录下给出的链接(JDK 的已安装目录结构)。它将带您进入一个页面,该页面描述已安装的 JDK 在您的计算机上的位置以及 JDK 根目录的每个目录的内容。`bin`目录包含构成 Java 命令、工具和实用程序的所有可执行文件。如果目录`bin`没有自动添加到环境变量`PATH`,请考虑手动添加,这样您就可以从任何目录启动 Java 可执行文件。
如果按照安装说明进行操作,您可能已经注意到目录下给出的链接(JDK 的已安装目录结构)。它将带您进入一个页面,该页面描述已安装的 JDK 在您的计算机上的位置以及 JDK 根目录的每个目录的内容。`bin`目录包含构成 Java 命令、工具和工具的所有可执行文件。如果目录`bin`没有自动添加到环境变量`PATH`,请考虑手动添加,这样您就可以从任何目录启动 Java 可执行文件。
在上一节中,我们已经演示了`Java`命令`java -version`。其他可用 Java 可执行文件(命令、工具、和实用程序[可以在 JavaSE 文档中找到](https://www.oracle.com/technetwork/java/javase/documentation/index.html)。点击链接 Java 平台标准版技术文档站点,然后点击下一页的链接工具参考。您可以通过单击每个可执行工具的链接来了解其更多信息。
在上一节中,我们已经演示了`Java`命令`java -version`。其他可用 Java 可执行文件(命令、工具、和工具[可以在 JavaSE 文档中找到](https://www.oracle.com/technetwork/java/javase/documentation/index.html)。点击链接 Java 平台标准版技术文档站点,然后点击下一页的链接工具参考。您可以通过单击每个可执行工具的链接来了解其更多信息。
您还可以使用以下选项之一在计算机上运行列出的每个可执行文件:`-?``-h``--help``-help`。它将显示可执行文件及其所有选项的简要说明。
......@@ -223,7 +223,7 @@ System.out.println("Hello, world!");
![](img/c1e644e0-7c7b-4c6c-93ff-5902d44f4bcc.png)
从现在开始,每次讨论代码示例时,我们都将使用`main()`方法以相同的方式运行它们。在进行此操作时,我们将不捕获屏幕截图,而是将结果放在评论中,因为这样的样式更容易遵循。例如,以下代码显示了以前的代码演示在这种样式下的外观:
从现在开始,每次讨论代码示例时,我们都将使用`main()`方法以相同的方式运行它们。在进行此操作时,我们将不捕获屏幕截图,而是将结果放在注释中,因为这样的样式更容易遵循。例如,以下代码显示了以前的代码演示在这种样式下的外观:
```
System.out.println("Hello, world!"); //prints: Hello, world!
......@@ -1101,7 +1101,7 @@ System.out.println(x);
# 控制流语句
当一个 Java 程序被执行时,它是一个语句一个语句地执行的。有些语句必须根据表达式求值的结果有条件地执行。这种语句被称为**控制流语句**,因为在计算机科学中,控制流(或控制流)是执行或评估单个语句的顺序。
当一个 Java 程序被执行时,它是一个语句一个语句地执行的。有些语句必须根据表达式求值的结果有条件地执行。这种语句被称为**控制流语句**,因为在计算机科学中,控制流(或控制流)是执行或求值单个语句的顺序。
控制流语句可以是以下语句之一:
......@@ -1199,7 +1199,7 @@ switchDemo1(5); //prints: 5 or 6: 5
```
你可以从评论中看到结果。
你可以从注释中看到结果。
如果在每种情况下都要执行几行代码,您可以在代码块周围加上大括号`{}`,如下所示:
......@@ -1248,7 +1248,7 @@ switchDemo2(2); //prints: false
* `do..while`语句
* `for`语句,也称为**循环语句**
`while`陈述如下:
`while`语句如下:
```
while (boolean expression){
......@@ -1382,7 +1382,7 @@ for (String s: list){
}
```
我们将在第 6 章、“数据结构、泛型和流行实用程序”中讨论集合。
我们将在第 6 章、“数据结构、泛型和流行工具”中讨论集合。
# 异常处理语句
......
......@@ -45,9 +45,9 @@
正如我们已经提到的,对象可以建立父子关系,并以这种方式共享属性和行为。例如,我们可以创建一个继承`Vehicle`类的属性(例如权重)和行为(速度计算)的`Car`类。此外,子类可以有自己的属性(例如,乘客数量)和特定于汽车的行为(例如,软减震)。但是,如果我们创建一个`Truck`类作为车辆的子类,它的额外卡车特定属性(例如有效载荷)和行为(硬减震)将不同。
据说,`Car`类或`Truck`类的每个对象都有一个`Vehicle`类的父对象。但是`Car``Truck`类的对象不共享特定的`Vehicle`对象(每次创建子对象时,首先创建一个新的父对象)。他们只分享父的行为。这就是为什么所有子对象可以有相同的行为,但状态不同。这是实现代码可重用性的一种方法。当对象行为必须动态更改时,它可能不够灵活。在这种情况下,对象组合(从其他类带来行为)或函数式编程更合适(参见第 13 章、“函数式编程”)。
据说,`Car`类或`Truck`类的每个对象都有一个`Vehicle`类的父对象。但是`Car``Truck`类的对象不共享特定的`Vehicle`对象(每次创建子对象时,首先创建一个新的父对象)。他们只分享父的行为。这就是为什么所有子对象可以有相同的行为,但状态不同。这是实现代码可重用性的一种方法。当对象行为必须动态更改时,它可能不够灵活。在这种情况下,对象组合(从其他类带来行为)或函数式编程更合适(参见第 13 章、“函数式编程”)。
有可能使孩子的行为与遗传行为不同。为了实现它,捕获行为的方法可以在`child`类中重新实现。据说孩子可以*覆盖*遗传的行为,我们将很快解释如何做(见“重载、覆盖和隐藏”一节)。例如,如果`Car`类有自己的速度计算方法,则不继承父类`Vehicle`的相应方法,而是使用在子类中实现的新速度计算方法。
有可能使子项的行为与遗传行为不同。为了实现它,捕获行为的方法可以在`child`类中重新实现。据说子项可以*覆盖*遗传的行为,我们将很快解释如何做(见“重载、覆盖和隐藏”一节)。例如,如果`Car`类有自己的速度计算方法,则不继承父类`Vehicle`的相应方法,而是使用在子类中实现的新速度计算方法。
父类的属性也可以继承(但不能覆盖)。然而,类属性通常被声明为私有的;它们不能被继承这就是封装的要点。参见“访问修饰符”部分中对各种访问级别的描述`public``protected``private`
......@@ -221,7 +221,7 @@ double doSomething(int s, String i){
有一种特殊类型的参数需要提及,因为它与所有其他类型的参数完全不同。它被声明为后跟三个点的类型。它被称为**可变参数****varargs**)。但是,首先,让我们简单地定义一下 Java 中的数组是什么。
**数组**是保存相同类型元素的数据结构。元素由数字索引引用。这就够了,现在。我们在第 6 章、“数据结构、泛型和流行实用程序”中更多地讨论数组
**数组**是保存相同类型元素的数据结构。元素由数字索引引用。这就够了,现在。我们在第 6 章、“数据结构、泛型和流行工具”中更多地讨论数组
让我们从一个例子开始。让我们使用可变参数声明方法参数:
......@@ -498,7 +498,7 @@ System.out.println(ref1.toString());
这就是为什么`toString()`方法经常被覆盖,甚至包括在 IDE 的服务中。
我们将在第 6 章、“数据结构、泛型和流行实用程序”中更详细地讨论`hashCode()``equals()`方法。
我们将在第 6 章、“数据结构、泛型和流行工具”中更详细地讨论`hashCode()``equals()`方法。
`getClass()``clone()`方法不常使用。`getClass()`方法返回`Class`类的一个对象,这个对象有许多提供各种系统信息的方法。最常用的方法是返回当前对象的类名的方法。`clone()`方法可以复制当前对象。只要当前对象的所有属性都是原始类型,它就可以正常工作。但是,如果存在引用类型属性,`clone()`方法必须重新实现,这样才能正确复制引用类型。否则,将只复制引用,而不复制对象本身。这种拷贝称为**浅拷贝**,在某些情况下可能已经足够好了。`protected`关键字表示只有该类的子类可以访问它(参见“包、导入和访问”部分)。
......
......@@ -374,7 +374,7 @@ System.out.println(b); //prints: true
以下是`java.lang.Enum`类中最常用的方法:
* `name()`:按声明时的拼写返回`enum`常量的标识符(例如`WINTER`)。
* `toString()`:默认返回与`name()`方法相同的值,但可以重写以返回任何其他`String`值。
* `toString()`:默认返回与`name()`方法相同的值,但可以覆盖以返回任何其他`String`值。
* `ordinal()`:返回声明时`enum`常量的位置(列表中第一个有`0`序数值)。
* `valueOf(Class enumType, String name)`:返回`enum`常量对象,其名称表示为`String`文本。
* `values()`:在`java.lang.Enum`类的文档中没有描述的静态方法。在[《Java 语言规范 8.9.3》](https://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.9.3)中,描述为隐式声明。[《Java™ 教程》](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)表示编译器在创建`enum`时会自动添加一些特殊方法,其中静态`values()`方法按声明顺序返回包含`enum`所有值的数组。
......@@ -556,7 +556,7 @@ System.out.println(obj); //prints: Is not changed
要比较内容,可以使用`equals()`方法。它在`String`类和数值类型包装类(`Integer``Float`等)中的实现正好可以比较对象的内容
然而,`java.lang.Object`类中的`equals()`方法实现只比较引用,这是可以理解的,因为子类可能拥有的内容种类繁多,而泛型内容比较的实现是不可行的。这意味着每一个需要有`equals()`方法来比较对象内容而不仅仅是引用的 Java 对象都必须重新实现`equals()`方法,从而在`java.lang.Object`类中重写其实现,如下所示:
然而,`java.lang.Object`类中的`equals()`方法实现只比较引用,这是可以理解的,因为子类可能拥有的内容种类繁多,而泛型内容比较的实现是不可行的。这意味着每一个需要有`equals()`方法来比较对象内容而不仅仅是引用的 Java 对象都必须重新实现`equals()`方法,从而在`java.lang.Object`类中覆盖其实现,如下所示:
```java
public boolean equals(Object obj) {
......@@ -577,7 +577,7 @@ public boolean equals(Object obj) {
```
如您所见,它从输入对象中提取基元`int`值,并将其与当前对象的基元值进行比较。它根本不比较对象引用
如您所见,它从输入对象中提取原始`int`值,并将其与当前对象的原始值进行比较。它根本不比较对象引用
另一方面,`String`类首先比较引用,如果引用的值不相同,则比较对象的内容:
......@@ -598,9 +598,9 @@ public boolean equals(Object anObject) {
}
```
`StringLatin1.equals()``StringUTF16.equals()`方法逐个字符比较值,而不仅仅是参考值。
`StringLatin1.equals()``StringUTF16.equals()`方法逐个字符比较值,而不仅仅是引用值。
类似地,如果应用代码需要按内容比较两个对象,则必须重写相应类中的`equals()`方法。例如,让我们看看熟悉的`DemoClass`类:
类似地,如果应用代码需要按内容比较两个对象,则必须覆盖相应类中的`equals()`方法。例如,让我们看看熟悉的`DemoClass`类:
```java
class DemoClass{
......@@ -635,7 +635,7 @@ public int hashCode() {
通过查看生成的代码,我们希望您注意以下几点:
* `@Override`的用法:它确保该方法覆盖某个祖先中的方法(具有相同的签名)。有了这个注释,如果您修改了方法并更改了签名(错误地或有意地),编译器(和您的 IDE)将立即引发一个错误,告诉您在任何祖先类中都没有具有这种签名的方法。因此,它有助于及早发现错误。
* `@Override`的用法:它确保该方法覆盖某个祖先中的方法(具有相同的签名)。有了这个注释,如果您修改了方法并更改了签名(错误地或有意地),编译器(和您的 IDE)将立即引发一个错误,告诉您在任何祖先类中都没有具有这种签名的方法。因此,它有助于及早发现错误。
* `java.util.Objects`类的用法:它有很多非常有用的方法,包括`equals()`静态方法,它不仅比较引用,还使用`equals()`方法:
```java
......@@ -646,7 +646,7 @@ public int hashCode() {
因为,正如我们前面所演示的,在`String`类中实现的`equals()`方法根据字符串的内容进行比较,符合我们的目的,因为`DemoClass`的方法`getProp()`返回一个字符串
* `hashCode()`方法:这个方法返回的整数唯一地标识这个特定的对象(但是请不要期望它在应用的不同运行之间是相同的)。如果唯一需要的方法是`equals()`,则不需要实现此方法。尽管如此,我们还是建议在`Set`或基于哈希码的另一个集合中收集此类的对象时使用它(我们将在第 6 章、“数据结构、泛型和流行实用程序”中讨论 Java 集合)
* `hashCode()`方法:这个方法返回的整数唯一地标识这个特定的对象(但是请不要期望它在应用的不同运行之间是相同的)。如果唯一需要的方法是`equals()`,则不需要实现此方法。尽管如此,我们还是建议在`Set`或基于哈希码的另一个集合中收集此类的对象时使用它(我们将在第 6 章、“数据结构、泛型和流行工具”中讨论 Java 集合)
这两种方法都在`Object`中实现,因为许多算法使用`equals()``hashCode()`方法,如果没有实现这些方法,应用可能无法工作。同时,对象在应用中可能不需要它们。但是,一旦您决定实现`equals()`方法,也可以实现`hasCode()`方法。此外,正如您所看到的,IDE 可以做到这一点而不需要任何开销。
......@@ -789,7 +789,7 @@ class TheChildClass extends TheParentClass{
`super`关键字引用父对象。我们已经在“构造器中的`this`关键字的用法”部分中看到了它的用法,因为必须先创建父类对象,然后才能创建当前对象。如果构造器的第一行不是`super()`,则表示父类有一个没有参数的构造器。
当方法被重写并且必须调用父类的方法时,`super`关键字特别有用:
当方法被覆盖并且必须调用父类的方法时,`super`关键字特别有用:
```java
class B {
......@@ -945,7 +945,7 @@ System.out.println(Double.valueOf(d2)
```
此外,每个包装器类都有允许将数值的`String`表示转换为相应的基元数值类型或引用类型的方法。例如:
此外,每个包装器类都有允许将数值的`String`表示转换为相应的原始数值类型或引用类型的方法。例如:
```java
byte b1 = Byte.parseByte("42");
......@@ -1064,7 +1064,7 @@ System.out.println(d3); //prints: 42
在本章中,您了解了什么是 Java 包,以及它们在组织代码和类可访问性(包括`import`语句和访问修饰符)方面所起的作用。您还熟悉了引用类型:类、接口、数组和枚举。任何引用类型的默认值为`null`,包括`String`类型。
现在您了解了引用类型是通过引用传递到方法中的,以及如何使用和重写`equals()`方法。您还学习了保留关键字和限制关键字的完整列表,了解了`this``super`关键字的含义和用法。
现在您了解了引用类型是通过引用传递到方法中的,以及如何使用和覆盖`equals()`方法。您还学习了保留关键字和限制关键字的完整列表,了解了`this``super`关键字的含义和用法。
本章最后描述了原始类型、包装类型和`String`字面值之间转换的过程和方法。
......@@ -1139,7 +1139,7 @@ System.out.println(d3); //prints: 42
4. 缩小会使值变小
12. 选择所有正确的语句:
1. 装箱限制了
1. 装箱限制了值
2. 拆箱将创建一个新值
3. 装箱创建引用类型对象
4. 拆箱将删除引用类型对象
\ No newline at end of file
......@@ -80,7 +80,7 @@ Java 中异常处理框架的目的是保护应用代码不受意外情况的影
所有异常都扩展了`java.lang.Exception`类,而`java.lang.Exception`类又扩展了`java.lang.Throwable`类。这就是为什么通过捕捉`java.lang.Exception`类的对象,代码捕捉任何异常类型的对象。我们已经在“Java 异常框架”一节中通过这种方式捕获了`java.lang.NullPointerException`进行了演示。
例外之一是`java.lang.RuntimeException`。扩展它的异常称为**运行时异常****非受检异常**。我们已经提到了其中的一些:`NullPointerException``ArrayIndexOutOfBoundsException``ClassCastException``NumberFormatException`。为什么它们被称为运行时异常是很清楚的,而为什么它们被称为非受检的异常将在下一段中变得很清楚。
异常之一是`java.lang.RuntimeException`。扩展它的异常称为**运行时异常****非受检异常**。我们已经提到了其中的一些:`NullPointerException``ArrayIndexOutOfBoundsException``ClassCastException``NumberFormatException`。为什么它们被称为运行时异常是很清楚的,而为什么它们被称为非受检的异常将在下一段中变得很清楚。
祖先中没有`java.lang.RuntimeException`的称为**检查异常**。这样命名的原因是编译器确保(检查)这些异常被捕获或列在方法的`throws`子句中(参见“`throws`语句”部分)。这种设计迫使程序员做出有意识的决定,要么捕获受检的异常,要么通知方法的客户端该异常可能由方法引发,并且必须由客户端处理(处理)。以下是一些受检异常的示例:
......@@ -222,7 +222,7 @@ void throwsDemo() throws SQLException {
我们去掉了`catch`子句,但是 Java 语法要求`catch``finally`块必须跟在`try`块后面,所以我们添加了一个空的`finally`
`throws`条款允许但不要求我们列出未经检查的例外情况。添加非受检的异常不会强制方法的用户处理它们。
`throws`条款允许但不要求我们列出非受检异常的情况。添加非受检的异常不会强制方法的用户处理它们。
最后,如果方法抛出几个不同的异常,可以列出基本的`Exception`异常类,而不是列出所有异常。这将使编译器感到高兴,但这并不是一个好的实践,因为它隐藏了方法用户可能期望的特定异常的细节。
......@@ -252,7 +252,7 @@ class MyUncheckedException extends RuntimeException{
}
```
注意注释*这里需要添加代码*。您可以像向任何其他常规类一样向自定义异常添加方法和属性,但程序员很少这样做。最佳实践甚至明确建议避免使用异常来驱动业务逻辑。例外应该是顾名思义,只包括例外的,非常罕见的情况。
注意注释*这里需要添加代码*。您可以像向任何其他常规类一样向自定义异常添加方法和属性,但程序员很少这样做。最佳实践甚至明确建议避免使用异常来驱动业务逻辑。异常应该是顾名思义,只包括异常的,非常罕见的情况。
但是,如果您需要宣布异常情况,请使用`throw`关键字和`new`运算符来创建并触发异常对象的传播。以下是几个例子:
......@@ -307,7 +307,7 @@ boolean assertSomething(int x, String y, double z){
* 如果有疑问,也可以在源代码附近捕获非受检的异常
* 尽可能靠近源处理异常,因为它是上下文最具体的地方,也是根本原因所在的地方
* 除非必须,否则不要抛出选中的异常,因为您强制为可能永远不会发生的情况生成额外代码
* 如果有必要,将第三方检查的异常转换为非受检的异常,方法是将它们作为`RuntimeException`重新抛出,并显示相应的消息
* 如果有必要,将第三方的受检异常转换为非受检的异常,方法是将它们作为`RuntimeException`重新抛出,并显示相应的消息
* 除非必须,否则不要创建自定义异常
* 除非必须,否则不要使用异常处理机制来驱动业务逻辑
* 通过使用消息系统和可选的枚举类型(而不是使用异常类型)来定制泛型`RuntimeException`,以传达错误的原因
......
# 字符串、输入/输出和文件
在本章中,读者将更详细地了解`String`类方法。我们还将讨论标准库和 ApacheCommons 项目中流行的字符串实用程序。下面将概述 Java 输入/输出流和`java.io`包的相关类,以及`org.apache.commons.io`包的一些类。文件管理类及其方法在专用部分中进行了描述。
在本章中,读者将更详细地了解`String`类方法。我们还将讨论标准库和 ApacheCommons 项目中流行的字符串工具。下面将概述 Java 输入/输出流和`java.io`包的相关类,以及`org.apache.commons.io`包的一些类。文件管理类及其方法在专用部分中进行了描述。
本章将讨论以下主题:
* 字符串处理
* I/O 流
* 文件管理
* Apache Commons 实用程序`FileUtils``IOUtils`
* Apache Commons 工具`FileUtils``IOUtils`
# 字符串处理
在主流编程中,`String`可能是最流行的类。在[第一章](01.html)“Java12 入门”中,我们了解了这个类,它的文本和它的特殊特性**字符串不变性**。在本节中,我们将解释如何使用标准库中的`String`类方法和实用程序类处理字符串,特别是使用`org.apache.commons.lang3`包中的`StringUtils`类。
在主流编程中,`String`可能是最流行的类。在[第一章](01.html)“Java12 入门”中,我们了解了这个类,它的文本和它的特殊特性**字符串不变性**。在本节中,我们将解释如何使用标准库中的`String`类方法和工具类处理字符串,特别是使用`org.apache.commons.lang3`包中的`StringUtils`类。
# 字符串类的方法
......@@ -275,7 +275,7 @@ line.lines().forEach(System.out::println);
我们将在第 14 章、“Java 标准流”中讨论流。
# 字符串实用程序
# 字符串工具
除了`String`类之外,还有许多其他类具有处理`String`值的方法。其中最有用的是来自一个名为 **Apache Commons** 的项目的`org.apache.commons.lang3`包的`StringUtils`类,该项目由名为 **Apache Software Foundation** 的开源程序员社区维护。我们将在第 7 章、“Java 标准和外部库”中详细介绍这个项目及其库。要在项目中使用它,请在`pom.xml`文件中添加以下依赖项:
......@@ -377,7 +377,7 @@ System.out.println("'" + StringUtils.trimToEmpty(" ") + "'"); // ''
在 Java 类库中,`InputStream`抽象类有以下直接实现:`ByteArrayInputStream``FileInputStream``ObjectInputStream``PipedInputStream``SequenceInputStream``FilterInputStream``javax.sound.sampled.AudioInputStream`
它们要么按原样使用,要么重写`InputStream`类的以下方法:
它们要么按原样使用,要么覆盖`InputStream`类的以下方法:
* `int available()`:返回可读取的字节数
* `void close()`:关闭流并释放资源
......@@ -394,7 +394,7 @@ System.out.println("'" + StringUtils.trimToEmpty(" ") + "'"); // ''
* `long skip(long n)`:跳过流的`n`或更少字节;返回实际跳过的字节数
* `long transferTo(OutputStream out)`:从输入流读取数据,逐字节写入提供的输出流;返回实际传输的字节数
`abstract int read()`是唯一必须实现的方法,但是这个类的大多数后代也重写了许多其他方法。
`abstract int read()`是唯一必须实现的方法,但是这个类的大多数后代也覆盖了许多其他方法。
# 字节数组输入流
......@@ -516,7 +516,7 @@ try(InputStream is = InputOutputStream.class.getResourceAsStream("/hello.txt")){
`ObjectInputStream`类的方法集比任何其他`InputStream`实现的方法集大得多。原因是它是围绕读取对象字段的值构建的,对象字段可以是各种类型的。为了使`ObjectInputStream`能够从输入的数据流构造一个对象,该对象必须是*可反序列化的*,这意味着它首先必须是*可序列化的*,可以转换成字节流。通常,这样做是为了通过网络传输对象。在目标位置,序列化对象被反序列化,原始对象的值被还原。
基本类型和大多数 Java 类,包括`String`类和基本类型包装器,都是可序列化的。如果类具有自定义类型的字段,则必须通过实现`java.io.Serizalizable`使其可序列化。怎么做不在这本书的范围之内。现在,我们只使用可序列化类型。我们来看看这门课
基本类型和大多数 Java 类,包括`String`类和基本类型包装器,都是可序列化的。如果类具有自定义类型的字段,则必须通过实现`java.io.Serizalizable`使其可序列化。怎么做不在这本书的范围之内。现在,我们只使用可序列化类型。我们来看看这个类
```java
class SomeClass implements Serializable {
......@@ -676,7 +676,7 @@ public int read(byte b[]) throws IOException {
```
`InputStream`类的所有其他方法都被类似地重写;函数被委托给分配给`in`属性的对象。
`InputStream`类的所有其他方法都被类似地覆盖;函数被委托给分配给`in`属性的对象。
如您所见,构造器是受保护的,这意味着只有子级可以访问它。这样的设计对客户端隐藏了流的实际来源,并迫使程序员使用`FilterInputStream`类扩展之一:`BufferedInputStream``CheckedInputStream``DataInputStream``PushbackInputStream``javax.crypto.CipherInputStream``java.util.zip.DeflaterInputStream``java.util.zip.InflaterInputStream``java.security.DigestInputStream``javax.swing.ProgressMonitorInputStream`。或者,可以创建自定义扩展。但是,在创建自己的扩展之前,请查看列出的类,看看其中是否有一个适合您的需要。下面是一个使用`BufferedInputStream`类的示例:
......@@ -728,7 +728,7 @@ try(FileInputStream fis =
`FileOutputStream`类有以下直接扩展:`BufferedOutputStream``CheckedOutputStream``DataOutputStream``PrintStream``javax.crypto.CipherOutputStream``java.util.zip.DeflaterOutputStream``java.security.DigestOutputStream``java.util.zip.InflaterOutputStream`
它们要么按原样使用,要么重写`OutputStream`类的以下方法:
它们要么按原样使用,要么覆盖`OutputStream`类的以下方法:
* `void close()`:关闭流并释放资源
* `void flush()`:强制写出剩余字节
......@@ -737,7 +737,7 @@ try(FileInputStream fis =
* `void write(byte[] b, int off, int len)`:从`off`偏移量开始,将所提供字节数组的`len`字节写入输出流
* `abstract void write(int b)`:将提供的字节写入输出流
唯一需要实现的方法是`abstract void write(int b)`,但是`OutputStream`类的大多数后代也重写了许多其他方法
唯一需要实现的方法是`abstract void write(int b)`,但是`OutputStream`类的大多数后代也覆盖了许多其他方法
在学习了“类`InputStream`及其子类”部分中的输入流之后,除了`PrintStream`类之外的所有`OutputStream`实现都应该对您非常熟悉。所以,我们在这里只讨论`PrintStream`类。
......@@ -862,7 +862,7 @@ System.out.printf("Hi ")
* `long skip(long n)`:尝试跳过`n`个字符;返回跳过字符的计数
* `long transferTo(Writer out)`:从该读取器读取所有字符,并将字符写入提供的`Writer`对象
如您所见,唯一需要实现的方法是两个抽象的`read()``close()`方法。然而,这个类的许多子类也重写了其他方法,有时是为了更好的性能或不同的功能。JCL 中的`Reader`子类是:`CharArrayReader``InputStreamReader``PipedReader``StringReader``BufferedReader``FilterReader``BufferedReader`类有`LineNumberReader`子类,`FilterReader`类有`PushbackReader`子类。
如您所见,唯一需要实现的方法是两个抽象的`read()``close()`方法。然而,这个类的许多子类也覆盖了其他方法,有时是为了更好的性能或不同的功能。JCL 中的`Reader`子类是:`CharArrayReader``InputStreamReader``PipedReader``StringReader``BufferedReader``FilterReader``BufferedReader`类有`LineNumberReader`子类,`FilterReader`类有`PushbackReader`子类。
# `Writer`及其子类
......@@ -880,7 +880,7 @@ System.out.printf("Hi ")
* `void write(String str)`:写入提供的字符串
* `void write(String str, int off, int len)`:从`off`索引开始,从提供的`str`字符串写入一个`len`长度的子字符串
如您所见,三个抽象方法:`write(char[], int, int)``flush()``close()`必须由这个类的子类实现,它们通常也重写其他方法。
如您所见,三个抽象方法:`write(char[], int, int)``flush()``close()`必须由这个类的子类实现,它们通常也覆盖其他方法。
JCL 中的`Writer`子类是:`CharArrayWriter``OutputStreamWriter``PipedWriter``StringWriter``BufferedWriter``FilterWriter``PrintWriter``OutputStreamWriter`类有一个`FileWriter`子类。
......@@ -1354,7 +1354,7 @@ try {
}
```
这个例子创建和删除一个文件和所有相关的目录,注意我们在“字符串实用程序”一节中讨论的`org.apache.commons.lang3.StringUtils`类的用法。它允许我们从路径中删除刚刚删除的目录,并继续这样做,直到所有嵌套的目录都被删除,而顶层目录最后被删除
这个例子创建和删除一个文件和所有相关的目录,注意我们在“字符串工具”一节中讨论的`org.apache.commons.lang3.StringUtils`类的用法。它允许我们从路径中删除刚刚删除的目录,并继续这样做,直到所有嵌套的目录都被删除,而顶层目录最后被删除
# 列出文件和目录
......@@ -1417,11 +1417,11 @@ for(File f: File.listRoots()){
但是,对文件过滤器的讨论超出了本书的范围。
# Apache 公共实用程序`FileUtils`和`IOUtils`
# Apache 公共工具`FileUtils`和`IOUtils`
JCL 最流行的伙伴是 [ApacheCommons 项目](https://commons.apache.org),它提供了许多库来补充 JCL 功能。`org.apache.commons.io`包的类包含在以下根包和子包中:
* `org.apache.commons.io`根包包含用于常见任务的带有静态方法的实用程序类,例如分别在“类`FileUtils`”和“类`IOUtils`”小节中描述的流行的`FileUtils`和`IOUtils`类
* `org.apache.commons.io`根包包含用于常见任务的带有静态方法的工具类,例如分别在“类`FileUtils`”和“类`IOUtils`”小节中描述的流行的`FileUtils`和`IOUtils`类
* `org.apache.commons.io.input`包包含支持基于`InputStream`和`Reader`实现的输入的类,如`XmlStreamReader`或`ReversedLinesFileReader`
* `org.apache.commons.io.output`包包含支持基于`OutputStream`和`Writer`实现的输出的类,如`XmlStreamWriter`或`StringBuilderWriter`
......@@ -1451,7 +1451,7 @@ JCL 最流行的伙伴是 [ApacheCommons 项目](https://commons.apache.org),
# `IOUtils`类
`org.apache.commons.io.IOUtils`是另一个非常有用的实用程序类,提供以下通用 IO 流操作方法:
`org.apache.commons.io.IOUtils`是另一个非常有用的工具类,提供以下通用 IO 流操作方法:
* `closeQuietly`:关闭流的方法,忽略空值和异常
* `toXxx/read`:从流中读取数据的方法
......@@ -1465,9 +1465,9 @@ JCL 最流行的伙伴是 [ApacheCommons 项目](https://commons.apache.org),
# 总结
在本章中,我们讨论了允许分析、比较和转换字符串的`String`类方法。我们还讨论了 JCL 和 ApacheCommons 项目中流行的字符串实用程序。本章的两个主要部分专门介绍 JCL 和 ApacheCommons 项目中的输入/输出流和支持类。文中还讨论了文件管理类及其方法,并给出了具体的代码实例。
在本章中,我们讨论了允许分析、比较和转换字符串的`String`类方法。我们还讨论了 JCL 和 ApacheCommons 项目中流行的字符串工具。本章的两个主要部分专门介绍 JCL 和 ApacheCommons 项目中的输入/输出流和支持类。文中还讨论了文件管理类及其方法,并给出了具体的代码实例。
在下一章中,我们将介绍 Java 集合框架及其三个主要接口`List`、`Set`和`Map`,包括泛型的讨论和演示。我们还将讨论用于管理数组、对象和时间/日期值的实用程序类。
在下一章中,我们将介绍 Java 集合框架及其三个主要接口`List`、`Set`和`Map`,包括泛型的讨论和演示。我们还将讨论用于管理数组、对象和时间/日期值的工具类。
# 测验
......
# 数据结构、泛型和流行实用程序
# 数据结构、泛型和流行工具
本章介绍了 Java 集合框架及其三个主要接口:`List``Set``Map`,包括泛型的讨论和演示。`equals()``hashCode()`方法也在 Java 集合的上下文中讨论。用于管理数组、对象和时间/日期值的实用程序类也有相应的专用部分。
本章介绍了 Java 集合框架及其三个主要接口:`List``Set``Map`,包括泛型的讨论和演示。`equals()``hashCode()`方法也在 Java 集合的上下文中讨论。用于管理数组、对象和时间/日期值的工具类也有相应的专用部分。
本章将讨论以下主题:
* `List``Set``Map`接口
* 集合实用程序
* 数组实用程序
* 对象实用程序
* 集合工具
* 数组工具
* 对象工具
* `java.time`
# 列表、集合和映射接口
......@@ -90,7 +90,7 @@ System.out.println(coll); //prints: [s3, s4]
```
正如人们所料,`Set`的工厂方法不允许重复,因此我们已经注释掉了该行(否则,前面的示例将停止在该行运行)。不太令人期待的是,不能有一个`null`元素,也不能在使用`of()`方法之一初始化集合之后添加/删除/修改集合的元素。这就是为什么我们注释掉了前面示例中的一些行。如果需要在集合初始化后添加元素,则必须使用构造器或其他创建可修改集合的实用程序对其进行初始化(稍后我们将看到一个`Arrays.asList()`的示例)。
正如人们所料,`Set`的工厂方法不允许重复,因此我们已经注释掉了该行(否则,前面的示例将停止在该行运行)。不太令人期待的是,不能有一个`null`元素,也不能在使用`of()`方法之一初始化集合之后添加/删除/修改集合的元素。这就是为什么我们注释掉了前面示例中的一些行。如果需要在集合初始化后添加元素,则必须使用构造器或其他创建可修改集合的工具对其进行初始化(稍后我们将看到一个`Arrays.asList()`的示例)。
接口`Collection`提供了两种向实现了`Collection``List``Set`的父接口)的对象添加元素的方法,如下所示:
......@@ -200,7 +200,7 @@ System.out.println(list3); //prints: [s1, s2]
# `java.lang.Iterable`接口
`Collection`接口扩展了`java.lang.Iterable`接口,这意味着那些直接或不直接实现`Collection`接口的类也实现了`java.lang.Iterable`接口。`Iterable`界面只有三种方式:
`Collection`接口扩展了`java.lang.Iterable`接口,这意味着那些直接或不直接实现`Collection`接口的类也实现了`java.lang.Iterable`接口。`Iterable`接口只有三种方式:
* `Iterator<T> iterator()`:返回实现接口`java.util.Iterator`的类的对象,允许集合在`FOR`语句中使用,例如:
......@@ -477,14 +477,14 @@ System.out.println(list); //prints: [Person{age=45, name=Kelly}]
如您所见,尽管无法将元素添加到由`of()`工厂方法创建的列表中,但是如果对元素的引用存在于列表之外,则仍然可以修改其元素。
# 集合实用程序
# 集合工具
有两个类具有处理集合的静态方法,它们非常流行并且非常有用:
* `java.util.Collections`
* `org.apache.commons.collections4.CollectionUtils`
这些方法是静态的,这意味着它们不依赖于对象状态,因此它们也被称为**无状态方法****实用程序方法**
这些方法是静态的,这意味着它们不依赖于对象状态,因此它们也被称为**无状态方法****工具方法**
# `java.util.Collections`类
......@@ -571,7 +571,7 @@ System.out.println(persons); //prints: [Person{name=Bob, age=15},
Person{name=Jack, age=23}]
```
正如我们已经提到的,`Collections`类中还有更多的实用程序,因此我们建议您至少查看一次它的文档并查看所有的功能。
正如我们已经提到的,`Collections`类中还有更多的工具,因此我们建议您至少查看一次它的文档并查看所有的功能。
# ApacheCommons `CollectionUtils`类
......@@ -587,7 +587,7 @@ ApacheCommons 项目中的`org.apache.commons.collections4.CollectionUtils`类
</dependency>
```
这个类中有很多方法,随着时间的推移,可能会添加更多的方法。这些实用程序是在`Collections`方法之外创建的,因此它们更复杂、更细致,不适合本书的范围。为了让您了解`CollectionUtils`类中可用的方法,以下是根据功能分组的方法的简短说明:
这个类中有很多方法,随着时间的推移,可能会添加更多的方法。这些工具是在`Collections`方法之外创建的,因此它们更复杂、更细致,不适合本书的范围。为了让您了解`CollectionUtils`类中可用的方法,以下是根据功能分组的方法的简短说明:
* 从集合中检索元素的方法
* 向集合中添加元素或元素组的方法
......@@ -601,9 +601,9 @@ ApacheCommons 项目中的`org.apache.commons.collections4.CollectionUtils`类
* 检查集合大小和空性的方法
* 反转数组的方法
最后一个方法可能属于处理数组的实用程序类。这就是我们现在要讨论的。
最后一个方法可能属于处理数组的工具类。这就是我们现在要讨论的。
# 数组实用程序
# 数组工具
有两个类具有处理集合的静态方法,它们非常流行并且非常有用:
......@@ -614,7 +614,7 @@ ApacheCommons 项目中的`org.apache.commons.collections4.CollectionUtils`类
# `java.util.Arrays`类
我们已经用过几次了。它是数组管理的主要实用程序类。这个实用程序类过去非常流行,因为有`asList(T...a)`方法。它是创建和初始化集合的最简洁的方法:
我们已经用过几次了。它是数组管理的主要工具类。这个工具类过去非常流行,因为有`asList(T...a)`方法。它是创建和初始化集合的最简洁的方法:
```java
List<String> list = Arrays.asList("s0", "s1");
......@@ -636,7 +636,7 @@ Set<String> set = new HashSet<>(Arrays.asList("s0", "s1");
* `splititerator()`:返回`Splititerator`对象,对数组或数组的一部分进行并行处理(由索引的范围指定)
* `stream()`:生成数组元素流或其中的一部分(由索引的范围指定);参见第 14 章、“Java 标准流”
所有这些方法都是有用的,但我们想提请您注意`equals(a1, a2)`方法和`deepEquals(a1, a2)`。它们对于数组比较特别有用,因为数组对象不能实现`equals()`自定义方法,而是使用`Object`类的实现(只比较引用)。`equals(a1, a2)``deepEquals(a1, a2)`方法不仅允许比较`a1``a2`参考文献,还可以使用`equals()`方法比较元素。以下是演示这些方法如何工作的代码示例:
所有这些方法都是有用的,但我们想提请您注意`equals(a1, a2)`方法和`deepEquals(a1, a2)`。它们对于数组比较特别有用,因为数组对象不能实现`equals()`自定义方法,而是使用`Object`类的实现(只比较引用)。`equals(a1, a2)``deepEquals(a1, a2)`方法不仅允许比较`a1``a2`引用,还可以使用`equals()`方法比较元素。以下是演示这些方法如何工作的代码示例:
```java
String[] arr1 = {"s1", "s2"};
......@@ -682,9 +682,9 @@ System.out.println(Arrays.deepEquals(arr3, arr4)); //prints: true
* `subarray()`:根据索引的范围提取数组的一部分
* `toMap()``toObject()``toPrimitive()``toString()``toStringArray()`:将数组转换为其他类型,并处理`null`
# 对象实用程序
# 对象工具
本节中描述的两个实用程序是:
本节中描述的两个工具是:
* `java.util.Objects`
* `org.apache.commons.lang3.ObjectUtils`
......@@ -844,7 +844,7 @@ for(String e: list){
}
```
在撰写本文时,`Objects`类有 17 种方法。我们建议您熟悉它们,以避免在已经存在相同实用程序的情况下编写自己的实用程序
在撰写本文时,`Objects`类有 17 种方法。我们建议您熟悉它们,以避免在已经存在相同工具的情况下编写自己的工具
# ApacheCommons `ObjectUtils`类
......@@ -858,7 +858,7 @@ for(String e: list){
</dependency>
```
`ObjectUtils`的所有方法可分为七组:
`ObjectUtils`的所有方法可分为七组:
* 对象克隆方法
* 比较两个对象的方法
......@@ -885,7 +885,7 @@ for(String e: list){
# `LocalDate`类
班级`LocalDate`不带时间。它表示 ISO 8601 格式的日期(YYYY-MM-DD):
`LocalDate`不带时间。它表示 ISO 8601 格式的日期(YYYY-MM-DD):
```java
System.out.println(LocalDate.now()); //prints: 2019-03-04
......@@ -1187,7 +1187,7 @@ System.out.println(duration.getNano()); //prints: 0
本章向读者介绍了 Java 集合框架及其三个主要接口:`List``Set``Map`。讨论了每个接口,并用其中一个实现类演示了其方法。对泛型也进行了解释和演示。必须实现`equals()``hashCode()`方法,以便 Java 集合能够正确处理对象。
实用程序`Collections``CollectionUtils`有许多有用的集合处理方法,并在示例中介绍了它们,以及`Arrays``ArrayUtils``Objects``ObjectUtils`
工具`Collections``CollectionUtils`有许多有用的集合处理方法,并在示例中介绍了它们,以及`Arrays``ArrayUtils``Objects``ObjectUtils`
`java.time`包的类的方法允许管理时间/日期值,这在特定的实际代码片段中得到了演示。
......
......@@ -52,7 +52,7 @@ JCL 是实现该语言的包的集合。更简单地说,它是 JDK 中包含
* `Throwable`类:所有异常的基类
* `Error`类:一个异常类,它的所有子类都用来传递应用不应该捕捉到的系统错误
* `Exception`类:该类及其直接子类表示选中的异常
* `RuntimeException`类:这个类及其子类表示未检查的异常,也称为运行时异常
* `RuntimeException`类:这个类及其子类表示非受检异常,也称为运行时异常
* `ClassLoader`类:读取`.class`文件并将其放入(装入)内存;也可以用来构建定制的类装入器
* `Process``ProcessBuilder`类:允许创建其他 JVM 进程
* 许多其他有用的类和接口
......@@ -64,21 +64,21 @@ JCL 是实现该语言的包的集合。更简单地说,它是 JDK 中包含
* `Collection`接口:集合的许多其他接口的基础接口,它声明了管理集合元素所需的所有基本方法:`size()``add()``remove()``contains()``stream()`等;它还扩展了`java.lang.Iterable`接口,继承了`iterator()``forEach()`等方法,这意味着`Collection`接口的任何实现或其任何子接口`List``Set``Queue``Deque`等也可以用于迭代语句中:`ArrayList``LinkedList``HashSet``AbstractQueue``ArrayDeque`
* `Map`接口和实现它的类:`HashMap``TreeMap`
* `Collections`类:提供许多静态方法来分析、操作和转换集合
* 许多其他集合接口、类和相关实用程序
* 许多其他集合接口、类和相关工具
我们在第 6 章、“数据结构、泛型和流行实用程序”中讨论了 Java 集合,并看到了它们的用法示例。
我们在第 6 章、“数据结构、泛型和流行工具”中讨论了 Java 集合,并看到了它们的用法示例。
`java.util`包还包括几个其他有用的类:
* `Objects`:提供了各种与对象相关的实用方法,其中一些我们已经在第 6 章、“数据结构、泛型和流行实用程序”中进行了概述
* `Arrays`:包含 160 种静态数组操作方法,其中一些方法我们在第 6 章、“数据结构、泛型和流行实用程序”中进行了概述
* `Formatter`:允许格式化任何原始类型`String``Date`和其他类型;我们在第 6 章、“数据结构、泛型和流行实用程序”中演示了它的用法示例
* `Objects`:提供了各种与对象相关的实用方法,其中一些我们已经在第 6 章、“数据结构、泛型和流行工具”中进行了概述
* `Arrays`:包含 160 种静态数组操作方法,其中一些方法我们在第 6 章、“数据结构、泛型和流行工具”中进行了概述
* `Formatter`:允许格式化任何原始类型`String``Date`和其他类型;我们在第 6 章、“数据结构、泛型和流行工具”中演示了它的用法示例
* `Optional``OptionalInt``OptionalLong``OptionalDouble`:这些类通过包装实际值来避免`NullPointerException`,实际值可以是`null`,也可以不是`null`
* `Properties`:帮助读取和创建用于应用配置和类似目的的键值对
* `Random`:通过生成伪随机数流来补充`java.lang.Math.random()`方法
* `StringTokeneizer`:将`String`对象分解为由指定分隔符分隔的标记
* `StringJoiner`:构造一个字符序列,由指定的分隔符分隔,并可选地由指定的前缀和后缀包围
* 许多其他有用的实用程序类,包括支持国际化和 Base64 编码和解码的类
* 许多其他有用的工具类,包括支持国际化和 Base64 编码和解码的类
# `java.time`
......@@ -94,7 +94,7 @@ JCL 是实现该语言的包的集合。更简单地说,它是 JDK 中包含
* `java.time.format.DateTimeFormatter`类允许按照**国际标准组织****ISO**)格式,如`YYYY-MM-DD`等格式显示日期和时间
* 其他一些支持日期和时间操作的类
我们在第 6 章、“数据结构、泛型和流行实用程序”中讨论了大多数此类。
我们在第 6 章、“数据结构、泛型和流行工具”中讨论了大多数此类。
# `java.io`以及`java.nio`
......@@ -172,8 +172,8 @@ JavaFX 基于**层叠样式表**(**CSS**),将平滑动画、Web 视图、
4. 单击“创建新测试”
5. 单击要测试的类的方法的复选框
6. 使用`@Test`为生成的测试方法编写代码
7. 如有必要,添加带有`@Before``@After`的方法
6. 使用`@Test`为生成的测试方法编写代码
7. 如有必要,添加带有`@Before``@After`的方法
假设我们有以下类:
......@@ -388,20 +388,20 @@ Apache Commons 项目包括以下三个部分:
据文献记载,`org.apache.commons.lang3`包提供了高度可重用的静态实用方法,主要是为`java.lang`类增加价值。这里有几个值得注意的例子:
* `ArrayUtils`类:允许搜索和操作数组;我们在第 6 章、“数据结构、泛型和流行实用程序”中讨论和演示了它
* `ArrayUtils`类:允许搜索和操作数组;我们在第 6 章、“数据结构、泛型和流行工具”中讨论和演示了它
* `ClassUtils`类:提供类的元数据
* `ObjectUtils`类:检查`null`的对象数组,比较对象,以`null`安全的方式计算对象数组的中值和最小/最大值;我们在第 6 章、“数据结构、泛型和流行实用程序”中讨论并演示了它
* `ObjectUtils`类:检查`null`的对象数组,比较对象,以`null`安全的方式计算对象数组的中值和最小/最大值;我们在第 6 章、“数据结构、泛型和流行工具”中讨论并演示了它
* `SystemUtils`类:提供执行环境的相关信息
* `ThreadUtils`类:查找当前正在运行的线程的信息
* `Validate`类:验证单个值和集合,比较它们,检查`null`,匹配,并执行许多其他验证
* `RandomStringUtils`类:根据不同字符集的字符生成`String`对象
* `StringUtils`课堂:我们在第 5 章中讨论了“字符串、输入/输出和文件”
* `StringUtils`:我们在第 5 章中讨论了“字符串、输入/输出和文件”
# `collections4`
尽管从表面上看,`org.apache.commons.collections4`包的内容与`org.apache.commons.collections`包(即包的版本 3)的内容非常相似,但迁移到版本 4 可能不如在`import`语句中添加“4”那么顺利。版本 4 删除了不推荐使用的类,添加了泛型和其他与以前版本不兼容的特性。
要想得到一个在这个包或它的一个子包中不存在的集合类型或集合实用程序,必须很困难。以下只是包含的功能和实用程序的高级列表:
要想得到一个在这个包或它的一个子包中不存在的集合类型或集合工具,必须很困难。以下只是包含的功能和工具的高级列表:
* `Bag`集合接口,具有每个对象的多个副本
* 实现`Bag`接口的十几个类;例如,下面是如何使用`HashBag`类:
......@@ -432,7 +432,7 @@ Apache Commons 项目包括以下三个部分:
```
* `MapIterator`提供简单快速的地图迭代界面,例如:
* `MapIterator`提供简单快速的地图迭代接口,例如:
```java
IterableMap<Integer, String> map =
......@@ -467,8 +467,8 @@ Apache Commons 项目包括以下三个部分:
* `Comparator`接口的各种实现
* `Iterator`接口的各种实现
* 将数组和枚举转换为集合的类
* 允许测试或创建集合的并集、交集和闭包的实用程序
* `CollectionUtils``ListUtils``MapUtils``MultiMapUtils``MultiSetUtils``QueueUtils``SetUtils`以及许多其他特定于接口的实用程序
* 允许测试或创建集合的并集、交集和闭包的工具
* `CollectionUtils``ListUtils``MapUtils``MultiMapUtils``MultiSetUtils``QueueUtils``SetUtils`以及许多其他特定于接口的工具
[阅读包装文件](https://commons.apache.org/proper/commons-collections)了解更多细节。
......
......@@ -35,7 +35,7 @@ Java 有两个执行单元:进程和线程。一个**进程**通常代表整
# 扩展`Thread`类
创建线程的一种方法是扩展`java.lang.Thread`类并重写`run()`方法。例如:
创建线程的一种方法是扩展`java.lang.Thread`类并覆盖`run()`方法。例如:
```java
class MyThread extends Thread {
......@@ -60,7 +60,7 @@ class MyThread extends Thread {
}
```
如果未重写`run()`方法,则线程不执行任何操作。在我们的示例中,只要参数不等于字符串`"exit"`,线程就会每秒打印它的名称和其他属性;否则它就会退出。`pauseOneSecond()`方法如下:
如果未覆盖`run()`方法,则线程不执行任何操作。在我们的示例中,只要参数不等于字符串`"exit"`,线程就会每秒打印它的名称和其他属性;否则它就会退出。`pauseOneSecond()`方法如下:
```java
private static void pauseOneSecond(){
......@@ -899,4 +899,4 @@ System.out.println("\n" + list); //prints: [One, Two, Three, Three]
1. 避免内存一致性错误的唯一方法是声明`volatile`变量
2. 使用`volatile`关键字可以确保值在所有线程中的变化的可见性
3. 避免并发的方法之一是避免任何状态管理
4. 无状态实用程序方法不能有并发问题
\ No newline at end of file
4. 无状态工具方法不能有并发问题
\ No newline at end of file
......@@ -18,7 +18,7 @@ JVM 只是根据编码逻辑执行指令的执行器。它还发现并将应用
在深入了解 JVM 的工作原理之前,让我们回顾一下如何运行应用,记住以下语句是同义词:
* 运行/执行/启动主类
* 运行/执行/启动方法
* 运行/执行/启动`main`方法
* 运行/执行/启动/启动应用
* 运行/执行/启动/启动 JVM 或 Java 进程
......
......@@ -697,7 +697,7 @@ System.out.println("Exit the client...");
与使用`send()`方法(返回`HttpResponse`对象)的示例相比,`sendAsync()`方法返回`CompletableFuture<HttpResponse>`类的实例。如果您阅读了`CompletableFuture<T>`类的文档,您将看到它实现了`java.util.concurrent.CompletionStage`接口,该接口提供了许多可以链接的方法,并允许您设置各种函数来处理响应。
下面是在`CompletionStage`界面中声明的方法列表:`acceptEither``acceptEitherAsync``acceptEitherAsync``applyToEither``applyToEitherAsync``applyToEitherAsync``handle``handleAsync``handleAsync``runAfterBoth``runAfterBothAsync``runAfterBothAsync``runAfterEither``runAfterEitherAsync``runAfterEitherAsync``thenAccept``thenAcceptAsync``thenAcceptAsync``thenAcceptBoth``thenAcceptBothAsync``thenAcceptBothAsync``thenApply``thenApplyAsync``thenApplyAsync``thenCombine``thenCombineAsync``thenCombineAsync``thenCompose``thenComposeAsync``thenComposeAsync``thenRun``thenRunAsync``thenRunAsync``whenComplete``whenCompleteAsync``whenCompleteAsync`
下面是在`CompletionStage`接口中声明的方法列表:`acceptEither``acceptEitherAsync``acceptEitherAsync``applyToEither``applyToEitherAsync``applyToEitherAsync``handle``handleAsync``handleAsync``runAfterBoth``runAfterBothAsync``runAfterBothAsync``runAfterEither``runAfterEitherAsync``runAfterEitherAsync``thenAccept``thenAcceptAsync``thenAcceptAsync``thenAcceptBoth``thenAcceptBothAsync``thenAcceptBothAsync``thenApply``thenApplyAsync``thenApplyAsync``thenCombine``thenCombineAsync``thenCombineAsync``thenCompose``thenComposeAsync``thenComposeAsync``thenRun``thenRunAsync``thenRunAsync``whenComplete``whenCompleteAsync``whenCompleteAsync`
我们将在第 13 章、“函数式编程”中讨论函数以及如何将它们作为参数传递。现在,我们只需要提到,`resp -> System.out.println("Response: " + resp.statusCode() + " : " + resp.body())`构造表示与以下方法相同的功能:
......
......@@ -481,7 +481,7 @@ public class HelloWorldController {
}
```
表单将被显示,但按钮单击将不起任何作用。这就是我们在讨论独立于应用逻辑的用户界面开发时的意思。注意`@FXML`注。它使用 FXML 标记的 ID 将方法和属性绑定到 FXML 标记。以下是完整控制器实现的外观:
表单将被显示,但按钮单击将不起任何作用。这就是我们在讨论独立于应用逻辑的用户界面开发时的意思。注意`@FXML`注。它使用 FXML 标记的 ID 将方法和属性绑定到 FXML 标记。以下是完整控制器实现的外观:
```java
@FXML
......@@ -1054,7 +1054,7 @@ mps.play();
所有效果共享父级-抽象类`Effect`。`Node`类具有`setEffect(Effect e)`方法,这意味着可以将任何效果添加到任何节点。这是将效果应用于节点的主要方式,演员在舞台上产生场景(如果我们回想一下本章开头介绍的类比)
唯一的例外是`Blend`效果,这使得它的使用比其他效果的使用更加复杂。除了使用`setEffect(Effect e)`方法外,一些`Node`班的孩子还有`setBlendMode(BlendMode bm)`方法,可以调节图像重叠时如何相互融合。因此,可以以不同的方式设置不同的混合效果,以相互覆盖,并产生可能难以调试的意外结果。这就是为什么`Blend`效果的使用更加复杂,这就是为什么我们要开始概述`Blend`效果如何使用的原因。
唯一的例外是`Blend`效果,这使得它的使用比其他效果的使用更加复杂。除了使用`setEffect(Effect e)`方法外,一些`Node`类的子项还有`setBlendMode(BlendMode bm)`方法,可以调节图像重叠时如何相互融合。因此,可以以不同的方式设置不同的混合效果,以相互覆盖,并产生可能难以调试的意外结果。这就是为什么`Blend`效果的使用更加复杂,这就是为什么我们要开始概述`Blend`效果如何使用的原因。
有四个方面可以控制两个图像重叠区域的外观(我们在示例中使用两个图像使其更简单,但实际上,许多图像可以重叠):
......@@ -1485,7 +1485,7 @@ sep.setLevel(d);
2. JavaFX 中所有场景参与者的基类是什么?
3. 说出 JavaFX 应用的基类。
4. JavaFX 应用必须实现的一种方法是什么?
5. 方法必须调用哪个`Application`方法来执行 JavaFX 应用?
5. `main`方法必须调用哪个`Application`方法来执行 JavaFX 应用?
6. 执行 JavaFX 应用需要哪两个 VM 选项?
7. 当使用上角的 x 按钮关闭 JavaFX 应用窗口时,调用哪个`Application`方法?
8. 必须使用哪个类来嵌入 HTML?
......
......@@ -12,7 +12,7 @@
# 什么是函数式编程?
在前面的章节中,我们实际使用了函数式编程。在第 6 章、“数据结构、泛型和流行实用程序”中,我们讨论了`Iterable`接口及其`default void forEach (Consumer<T> function)`方法,并提供了以下示例:
在前面的章节中,我们实际使用了函数式编程。在第 6 章、“数据结构、泛型和流行工具”中,我们讨论了`Iterable`接口及其`default void forEach (Consumer<T> function)`方法,并提供了以下示例:
```java
Iterable<String> list = List.of("s1", "s2", "s3");
......@@ -115,7 +115,7 @@ interface D extends C {
`A`是一个函数式接口,因为它只有一个抽象方法`method1()``B`也是一个函数式接口,因为它只有一个抽象方法,即从`A`接口继承的相同`method1()``C`是一个函数式接口,因为它只有一个抽象方法`method1()`,它覆盖父接口`A`的抽象`method1()`。接口`D`不能是函数式接口,因为它有两个抽象方法-`method1()`来自父接口`A``method5()`
为了避免运行时错误,Java8 中引入了`@FunctionalInterface`。它将意图告诉编译器,以便编译器可以检查并查看在带注释的接口中是否确实只有一个抽象方法。此注释还警告读代码的程序员,此接口故意只有一个抽象方法。否则,程序员可能会浪费时间将另一个抽象方法添加到接口中,结果在运行时发现它无法完成。
为了避免运行时错误,Java8 中引入了`@FunctionalInterface`。它将意图告诉编译器,以便编译器可以检查并查看在带注释的接口中是否确实只有一个抽象方法。此注释还警告读代码的程序员,此接口故意只有一个抽象方法。否则,程序员可能会浪费时间将另一个抽象方法添加到接口中,结果在运行时发现它无法完成。
出于同样的原因,`Runnable``Callable`接口从 Java 早期版本开始就存在,在 Java8 中被注释为`@FunctionalInterface`。这种区别是明确的,并提醒用户,这些接口可用于创建函数:
......
......@@ -12,11 +12,11 @@
# 作为数据和操作源的流
上一章中描述和演示的 Lambda 表达式以及函数式接口为 Java 添加了强大的函数编程功能。它们允许将行为(函数)作为参数传递给为数据处理性能而优化的库。通过这种方式,应用程序员可以专注于所开发系统的业务方面,而将性能方面留给专家——库的作者。这样一个库的一个例子是包`java.util.stream`,这将是本章的重点。
上一章中描述和演示的 Lambda 表达式以及函数式接口为 Java 添加了强大的函数编程功能。它们允许将行为(函数)作为参数传递给为数据处理性能而优化的库。通过这种方式,程序员可以专注于所开发系统的业务方面,而将性能方面留给专家——库的作者。这样一个库的一个例子是包`java.util.stream`,这将是本章的重点。
在第 5 章“字符串、输入/输出和文件”中,我们谈到了 I/O 流作为数据源,但除此之外,它们对数据的进一步处理没有太大帮助。它们是基于字节或字符的,而不是基于对象的。只有先以编程方式创建并序列化对象之后,才能创建对象流。I/O 流只是到外部资源的连接,大部分是文件,其他的不多。然而,有时可以从 I/O 流转换到`java.util.stream.Stream`。例如,`BufferedReader`类的`lines()`方法将底层基于字符的流转换为`Stream<String>`对象。
另一方面,`java.util.stream`包的流面向对象集合的处理。在第 6 章“数据结构、泛型和流行实用程序”中,我们描述了`Collection`接口的两种方法,允许将集合元素作为流的元素读取:`default Stream<E> stream()``default Stream<E> parallelStream()`。我们还提到了`java.util.Arrays``stream()`方法。它有以下八个重载版本,用于将数组或数组的一部分转换为相应数据类型的流:
另一方面,`java.util.stream`包的流面向对象集合的处理。在第 6 章“数据结构、泛型和流行工具”中,我们描述了`Collection`接口的两种方法,允许将集合元素作为流的元素读取:`default Stream<E> stream()``default Stream<E> parallelStream()`。我们还提到了`java.util.Arrays``stream()`方法。它有以下八个重载版本,用于将数组或数组的一部分转换为相应数据类型的流:
* `static DoubleStream stream(double[] array)`
* `static DoubleStream stream(double[] array, int startInclusive, int endExclusive)`
......@@ -336,13 +336,13 @@ new Random().ints(5, 8).limit(5)
* `default IntStream chars()`:创建一个扩展`char`值的`int`零流
* `default IntStream codePoints()`:根据该序列创建代码点值流
还有一个`java.util.stream.StreamSupport`类,它包含库开发人员使用的静态低级实用程序方法。但我们不会再讨论它,因为这超出了本书的范围。
还有一个`java.util.stream.StreamSupport`类,它包含库开发人员使用的静态低级工具方法。但我们不会再讨论它,因为这超出了本书的范围。
# 操作(方法)
`Stream`接口的许多方法,那些以函数式接口类型作为参数的方法,被称为**操作**,因为它们不是作为传统方法实现的。它们的功能作为函数传递到方法中。这些操作只是调用函数式接口的方法的 Shell,该函数式接口被指定为参数方法的类型。
例如,让我们看一下`Stream<T> filter (Predicate<T> predicate)`方法。它的实现是基于对`Predicate<T>`函数的方法`boolean test(T t)`的调用。因此,与其说*使用`Stream`对象的`filter()`方法来选择一些流元素并跳过其他流元素*,程序员更喜欢说,*应用一个操作过滤器,允许一些流元素通过并跳过其他流元素*。它描述动作(操作)的性质,而不是特定的算法,在方法接收到特定函数之前,算法是未知的。`Stream`界面有两组操作:
例如,让我们看一下`Stream<T> filter (Predicate<T> predicate)`方法。它的实现是基于对`Predicate<T>`函数的方法`boolean test(T t)`的调用。因此,与其说*使用`Stream`对象的`filter()`方法来选择一些流元素并跳过其他流元素*,程序员更喜欢说,*应用一个操作过滤器,允许一些流元素通过并跳过其他流元素*。它描述动作(操作)的性质,而不是特定的算法,在方法接收到特定函数之前,算法是未知的。`Stream`接口有两组操作:
* **中间操作**:返回`Stream`对象的实例方法
* **终端操作**:返回`Stream`以外类型的实例方法
......@@ -1105,7 +1105,7 @@ System.out.println(theOldest); //prints: Person{name='Jim', age=33}
当流是连续的时,从不调用组合器。但是当我们使它并行(`list.parallelStream()`)时,消息合并器被调用!打印了三次。好吧,在`reduce()`操作的情况下,部分结果的数量可能会有所不同,这取决于 CPU 的数量和`collect()`操作实现的内部逻辑。因此,消息组合器被称为!可打印任意次数
现在让我们看一下`collect()`操作的第一种形式。它需要实现`java.util.stream.Collector<T,A,R>`接口的类的对象,其中`T`是流类型,`A`是容器类型,`R`是结果类型。您可以使用以下方法之一`of()`(来自`Collector`界面)来创建必要的`Collector`对象:
现在让我们看一下`collect()`操作的第一种形式。它需要实现`java.util.stream.Collector<T,A,R>`接口的类的对象,其中`T`是流类型,`A`是容器类型,`R`是结果类型。您可以使用以下方法之一`of()`(来自`Collector`接口)来创建必要的`Collector`对象:
```java
static Collector<T,R,R> of(Supplier<R> supplier,
......
......@@ -227,7 +227,7 @@ public static interface Flow.Processor<T,R>
`Flow.Processor`接口描述了一个既可以充当订阅者又可以充当发布者的实体。它允许创建此类处理器的链(管道),以便订阅者可以从发布者接收项目,对其进行转换,然后将结果传递给下一个订阅者或处理器。
在推送模式中,发布者可以在没有来自订户的任何请求的情况下呼叫`onNext()`。如果处理速度低于项目发布速度,订阅者可以使用各种策略来缓解压力。例如,它可以跳过项目或为临时存储创建一个缓冲区,希望项目生产速度会减慢,订户能够赶上
在推送模式中,发布者可以在没有来自订户的任何请求的情况下调用`onNext()`。如果处理速度低于项目发布速度,订阅者可以使用各种策略来缓解压力。例如,它可以跳过项目或为临时存储创建一个缓冲区,希望项目生产速度会减慢,订户能够赶上
这是 ReactiveStreams 计划为支持具有非阻塞背压的异步数据流而定义的最小接口集。如您所见,它允许订阅者和发布者相互交谈并协调传入数据的速率,从而为我们在“反应式”部分讨论的背压问题提供了多种解决方案。
......@@ -694,7 +694,7 @@ pauseMs(100);
`Observable``Flowable`中,方法的数量超过 500 个。这就是为什么在本节中,我们将提供一个概述和几个例子,帮助读者浏览可能的选项迷宫。
为了帮助了解全局,我们将所有操作符分为十类:转换、过滤、组合、从 XXX 转换、异常处理、生命周期事件处理、实用程序、条件和布尔、背压和可连接。
为了帮助了解全局,我们将所有操作符分为十类:转换、过滤、组合、从 XXX 转换、异常处理、生命周期事件处理、工具、条件和布尔、背压和可连接。
请注意,这些不是所有可用的运算符。您可以在[在线文档](http://reactivex.io/RxJava/2.x/javadoc/index.html)中看到更多信息。
......@@ -859,7 +859,7 @@ Observable.error(new RuntimeException("MyException"))
这些操作符在管道中任何位置发生的特定事件上都被调用。它们的工作方式类似于“处理”部分中描述的操作符。
这些操作符的格式是`doXXX()`,其中`XXX`是事件的名称:`onComplete``onNext``onError`等。并不是所有的课程都有,有些课程`Observable``Flowable``Single``Maybe``Completable`上略有不同。但是,我们没有空间列出所有这些类的所有变体,我们的概述将局限于`Observable`类的生命周期事件处理操作符的几个示例:
这些操作符的格式是`doXXX()`,其中`XXX`是事件的名称:`onComplete``onNext``onError`等。并不是所有的类都有,有些类`Observable``Flowable``Single``Maybe``Completable`上略有不同。但是,我们没有空间列出所有这些类的所有变体,我们的概述将局限于`Observable`类的生命周期事件处理操作符的几个示例:
* `doOnSubscribe(Consumer<Disposable> onSubscribe)`:当观察者订阅时执行
* `doOnNext(Consumer<T> onNext)`:当源可观测调用`onNext`时,应用提供的`Consumer`功能
......@@ -937,7 +937,7 @@ pauseMs(25);
# 条件与布尔
以下运算符(及其多个重载版本)允许评估一个或多个可观察对象或发射值,并相应地更改处理逻辑:
以下运算符(及其多个重载版本)允许求值一个或多个可观察对象或发射值,并相应地更改处理逻辑:
* `all(Predicate criteria)`:返回`Single<Boolean>``true`值,如果所有发出的值都符合提供的条件
* `amb()`:接受两个或多个源可观察对象,并仅从第一个开始发射的源可观察对象发射值
......
......@@ -57,7 +57,7 @@
* **Akka**:这是一个为 Java 和 Scala 构建高度并发、分布式和弹性的消息驱动应用的工具箱(`akka.io`
* **Bootique**:这是一个针对可运行 Java 应用的最低限度的固执己见的框架(`bootique.io`
* **Dropwizard**:这是一个 Java 框架,用于开发操作友好、高性能和 RESTful Web 服务([www.dropwizard.io](https://www.dropwizard.io/1.3.9/docs/))。
* **Jodd**:这是一套 1.7MB 以下的 Java 微框架、工具和实用程序[jodd.org 网站](https://jodd.org/))。
* **Jodd**:这是一套 1.7MB 以下的 Java 微框架、工具和工具[jodd.org 网站](https://jodd.org/))。
* **Lightbend Lagom**:这是一个基于 Akka 和 Play([的固执己见的微服务框架 www.lightbend.com](https://www.lightbend.com/))。
* **Ninja**:这是一个 [Java 的全栈框架](https://www.ninjaframework.org/)
* **Spotify-Apollo**:这是 Spotify 用来编写微服务的一组 Java 库(Spotify/Apollo)。
......
......@@ -106,7 +106,7 @@ public void testCode() {
但即使是这段代码也容易受到我们刚才描述的 JVM 优化的影响。
JMH 的创建是为了帮助避免这种情况和类似的陷阱。在“JMH 用法示例”部分,我们将向您展示如何使用 JMH 来解决死代码和常量折叠优化问题,使用`@State``Blackhole`对象。
JMH 的创建是为了帮助避免这种情况和类似的陷阱。在“JMH 用法示例”部分,我们将向您展示如何使用 JMH 来解决死代码和常量折叠优化问题,使用`@State``Blackhole`对象。
此外,JMH 不仅可以测量平均执行时间,还可以测量吞吐量和其他性能特性。
......@@ -148,7 +148,7 @@ public class BenchmarkDemo {
}
```
请注意`@Benchmark`。它告诉框架必须测量此方法的性能。如果您运行前面的`main()`方法,您将看到类似于以下内容的输出:
请注意`@Benchmark`。它告诉框架必须测量此方法的性能。如果您运行前面的`main()`方法,您将看到类似于以下内容的输出:
![](img/3316accf-de05-4c3a-a117-a67dd492be62.png)
......@@ -188,7 +188,7 @@ public class BenchmarkDemo {
![](img/3c6e563e-17e8-4735-9296-d2ccd8873449.png)
6. IDE 重新启动后,插件就可以使用了。现在,您不仅可以运行`main()`方法,而且如果您有几个带有`@Benchmark`的方法,还可以选择要执行的基准测试方法。要执行此操作,请从“运行”下拉菜单中选择“运行…”:
6. IDE 重新启动后,插件就可以使用了。现在,您不仅可以运行`main()`方法,而且如果您有几个带有`@Benchmark`的方法,还可以选择要执行的基准测试方法。要执行此操作,请从“运行”下拉菜单中选择“运行…”:
![](img/c77f55c5-2ee7-48fa-942d-c826cf22c1b6.png)
......@@ -235,11 +235,11 @@ public class BenchmarkDemo {
@BenchmarkMode(Mode.All)
```
所描述的参数以及我们将在本章后面讨论的所有参数都可以在方法和/或类级别进行设置。方法级别集值重写类级别值。
所描述的参数以及我们将在本章后面讨论的所有参数都可以在方法和/或类级别进行设置。方法级别集值覆盖类级别值。
# 输出时间单位
用于呈现结果的时间单位可以使用`@OutputTimeUnit`指定:
用于呈现结果的时间单位可以使用`@OutputTimeUnit`指定:
```java
@OutputTimeUnit(TimeUnit.NANOSECONDS)
......@@ -258,7 +258,7 @@ public class BenchmarkDemo {
# 分叉
在运行多个测试时,`@Fork`允许您将每个测试设置为在单独的进程中运行。例如:
在运行多个测试时,`@Fork`允许您将每个测试设置为在单独的进程中运行。例如:
```java
@Fork(10)
......@@ -406,7 +406,7 @@ class SomeClass{
# 使用`@Param`注解
有时,有必要对不同的输入数据集运行相同的基准测试。在这种情况下,`@Param`非常有用。
有时,有必要对不同的输入数据集运行相同的基准测试。在这种情况下,`@Param`非常有用。
`@Param` is a standard Java annotation used by various frameworks, for example, JUnit. It identifies an array of parameter values. The test with the `@Param` annotation will be run as many times as there are values in the array. Each test execution picks up a different value from the array.
......
......@@ -71,7 +71,7 @@ System.out.println(person3.hashCode()); //prints: 596512129
`person1``person2`引用及其哈希码是相等的,因为它们指向相同的对象(内存的相同区域和相同的地址),而`person3`引用指向另一个对象。
但实际上,正如我们在第 6 章、“数据结构、泛型和流行实用程序”中所描述的,我们希望对象的相等性基于所有或部分对象属性的值,因此这里是`equals()``hashCode()`方法的典型实现:
但实际上,正如我们在第 6 章、“数据结构、泛型和流行工具”中所描述的,我们希望对象的相等性基于所有或部分对象属性的值,因此这里是`equals()``hashCode()`方法的典型实现:
```java
@Override
......@@ -90,7 +90,7 @@ public int hashCode() {
}
```
它以前更复杂,但是使用`java.util.Objects`实用程序会更容易,特别是当您注意到`Objects.equals()`方法也处理`null`时。
它以前更复杂,但是使用`java.util.Objects`工具会更容易,特别是当您注意到`Objects.equals()`方法也处理`null`时。
我们已经将所描述的`equals()``hashCode()`方法的实现添加到`Person1`类中,并执行了相同的比较:
......@@ -108,15 +108,15 @@ System.out.println(person3.hashCode()); //prints: 2115012528
如您所见,我们所做的更改不仅使相同的对象相等,而且使具有相同属性值的两个不同对象相等。此外,哈希码值现在也基于相同属性的值。
在第 6 章、“数据结构、泛型和流行实用程序”中,我们解释了在实现`equals()`方法的同时实现`hasCode()`方法的重要性。
在第 6 章、“数据结构、泛型和流行工具”中,我们解释了在实现`equals()`方法的同时实现`hasCode()`方法的重要性。
`equals()`方法中建立等式和在`hashCode()`方法中进行散列计算时,使用完全相同的属性集是非常重要的。
`@Override`释放在这些方法前面可以确保它们确实覆盖`Object`类中的默认实现。否则,方法名中的输入错误可能会造成一种假象,即新的实现被使用了,而实际上它并没有被使用。事实证明,调试这种情况比仅仅添加`@Override`注释要困难和昂贵得多,如果该方法不重写任何内容,就会产生错误。
`@Override`解放在这些方法前面可以确保它们确实覆盖`Object`类中的默认实现。否则,方法名中的输入错误可能会造成一种假象,即新的实现被使用了,而实际上它并没有被使用。事实证明,调试这种情况比仅仅添加`@Override`注解要困难和昂贵得多,如果该方法不覆盖任何内容,就会产生错误。
# `compareTo()`方法
在第 6 章、“数据结构、泛型和流行实用程序”中,我们广泛使用了`compareTo()`方法(`Comparable`接口的唯一方法),并指出基于该方法建立的顺序(通过集合元素实现)称为**自然顺序**
在第 6 章、“数据结构、泛型和流行工具”中,我们广泛使用了`compareTo()`方法(`Comparable`接口的唯一方法),并指出基于该方法建立的顺序(通过集合元素实现)称为**自然顺序**
为了证明这一点,我们创建了`Person2`类:
......@@ -175,7 +175,7 @@ list.stream().forEach(System.out::println);
有三件事值得注意:
* 根据`Comparable`接口,`compareTo()`方法必须返回负整数、零或正整数,因为对象小于、等于或大于另一个对象。在我们的实现中,如果两个对象的相同属性的值不同,我们会立即返回结果。我们已经知道这个对象是*大**小*,不管其他属性是什么。但是比较两个对象的属性的顺序对最终结果有影响。它定义属性值影响顺序的优先级。
* 我们已将`List.of()`的结果放入`new ArrayList()`对象中。我们这样做是因为,正如我们在第 6 章、“数据结构、泛型和流行实用程序”中已经提到的,工厂方法`of()`创建的集合是不可修改的。不能在其中添加或删除任何元素,也不能更改元素的顺序,同时需要对创建的集合进行排序。我们使用了`of()`方法,只是因为它更方便并且提供了更短的表示法
* 我们已将`List.of()`的结果放入`new ArrayList()`对象中。我们这样做是因为,正如我们在第 6 章、“数据结构、泛型和流行工具”中已经提到的,工厂方法`of()`创建的集合是不可修改的。不能在其中添加或删除任何元素,也不能更改元素的顺序,同时需要对创建的集合进行排序。我们使用了`of()`方法,只是因为它更方便并且提供了更短的表示法
* 最后,使用`java.util.Objects`进行属性比较,使得实现比定制编码更简单、更可靠。
在实现`compareTo()`方法时,重要的是确保不违反以下规则:
......@@ -199,7 +199,7 @@ protected native Object clone() throws CloneNotSupportedException;
```
评论指出:
注释指出:
```java
/**
......@@ -208,9 +208,9 @@ protected native Object clone() throws CloneNotSupportedException;
***
```
此方法的默认结果按原样返回对象字段的副本,如果值是原始类型,则可以这样做。但是,如果对象属性包含对另一个对象的引用,则只复制引用本身,而不复制引用的对象本身。这就是为什么这种拷贝被称为**浅拷贝**。要获得一个**深度副本**,必须重写`clone()`方法并克隆引用对象的每个对象属性
此方法的默认结果按原样返回对象字段的副本,如果值是原始类型,则可以这样做。但是,如果对象属性包含对另一个对象的引用,则只复制引用本身,而不复制引用的对象本身。这就是为什么这种拷贝被称为**浅拷贝**。要获得一个**深度副本**,必须覆盖`clone()`方法并克隆引用对象的每个对象属性
在任何情况下,为了能够克隆一个对象,它必须实现`Cloneable`接口,并确保继承树上的所有对象(以及作为对象的属性)也实现`Cloneable`接口(除了`java.lang.Object`类)。`Cloneable`接口只是一个标记接口,它告诉编译器程序员有意识地决定允许克隆这个对象(无论是因为浅拷贝足够好还是因为`clone()`方法被重写)。试图对未实现`Cloneable`接口的对象调用`clone()`将导致`CloneNotSupportedException`
在任何情况下,为了能够克隆一个对象,它必须实现`Cloneable`接口,并确保继承树上的所有对象(以及作为对象的属性)也实现`Cloneable`接口(除了`java.lang.Object`类)。`Cloneable`接口只是一个标记接口,它告诉编译器程序员有意识地决定允许克隆这个对象(无论是因为浅拷贝足够好还是因为`clone()`方法被覆盖)。试图对未实现`Cloneable`接口的对象调用`clone()`将导致`CloneNotSupportedException`
这看起来已经很复杂了,但实际上,还有更多的陷阱。例如,假设`Person`类具有`Address`类类型的`address`属性。`Person`对象`p1`的浅拷贝`p2`将引用`Address`的同一对象,因此`p1.address == p2.address`。下面是一个例子。`Address`类如下:
......@@ -360,7 +360,7 @@ System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street
# `StringBuffer`和`StringBuilder`类
我们在第 6 章、“数据结构、泛型和流行实用程序”中讨论了`StringBuffer``StringBuilder`类之间的区别。我们这里不重复了。相反,我们只会提到,在单线程进程(这是绝大多数情况下)中,`StringBuilder`类是首选,因为它更快。
我们在第 6 章、“数据结构、泛型和流行工具”中讨论了`StringBuffer``StringBuilder`类之间的区别。我们这里不重复了。相反,我们只会提到,在单线程进程(这是绝大多数情况下)中,`StringBuilder`类是首选,因为它更快。
# `try-catch-finally`
......@@ -405,7 +405,7 @@ System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street
# 优先组合而不是继承
最初,面向对象编程的重点是继承,作为在对象之间共享公共功能的方式。继承甚至是我们在第 2 章、“Java 面向对象编程(OOP)”中所描述的四个面向对象编程原则之一。然而,实际上,这种功能共享方法在同一继承行中包含的类之间创建了太多的依赖关系。应用功能的演化通常是不可预测的,继承链中的一些类开始获取与类链的原始目的无关的功能。我们可以说,有一些设计解决方案允许我们不这样做,并保持原始类完好无损。但是,在实践中,这样的事情总是发生,子类可能会突然改变行为,仅仅因为它们通过继承获得了新的功能。我们不能选择我们的父,对吗?此外,封装方式是封装的另一个基础原则。
最初,面向对象编程的重点是继承,作为在对象之间共享公共功能的方式。继承甚至是我们在第 2 章、“Java 面向对象编程(OOP)”中所描述的四个面向对象编程原则之一。然而,实际上,这种功能共享方法在同一继承行中包含的类之间创建了太多的依赖关系。应用功能的演化通常是不可预测的,继承链中的一些类开始获取与类链的原始目的无关的功能。我们可以说,有一些设计解决方案允许我们不这样做,并保持原始类完好无损。但是,在实践中,这样的事情总是发生,子类可能会突然改变行为,仅仅因为它们通过继承获得了新的功能。我们不能选择我们的父,对吗?此外,封装方式是封装的另一个基础原则。
另一方面,组合允许我们选择和控制类的哪些功能可以使用,哪些可以忽略。它还允许对象保持轻,而不受继承的负担。这样的设计更灵活、更可扩展、更可预测。
......@@ -429,14 +429,14 @@ System.out.println(p2.getAddress().getStreet()); //prints: 25 Main Street
这给我们留下了唯一可能的结论:在编写代码时,我们必须确保它对人类来说是容易阅读和理解的,而不是对计算机来说。那些在这个行业工作了一段时间的人对几年前自己编写的代码感到困惑。一种是通过清晰和透明的意图来改进代码编写风格。
我们可以讨论评论的必要性,直到奶牛回到谷仓。我们绝对不需要注释来直接响应代码的功能。例如:
我们可以讨论注释的必要性,直到奶牛回到谷仓。我们绝对不需要注释来直接响应代码的功能。例如:
```java
//Initialize variable
int i = 0;
```
解释意图的评论更有价值:
解释意图的注释更有价值:
```java
// In case of x > 0, we are looking for a matching group
......@@ -449,10 +449,10 @@ int i = 0;
注释代码可能非常复杂。好的注释解释了意图并提供了帮助我们理解代码的指导。然而,程序员通常不会费心去写注释。反对写注释的论据通常包括两种:
* 注释必须与代码一起维护和发展,否则,它们可能会产生误导,但是没有工具可以提示程序员在更改代码的同时调整注释。因此,评论是危险的。
* 注释必须与代码一起维护和发展,否则,它们可能会产生误导,但是没有工具可以提示程序员在更改代码的同时调整注释。因此,注释是危险的。
* 代码本身的编写(包括变量和方法的名称选择)不需要额外的解释。
这两种说法都是正确的,但评论也确实非常有用,尤其是那些抓住意图的评论。此外,这样的注释往往需要较少的调整,因为代码意图不会经常更改(如果有的话)。
这两种说法都是正确的,但注释也确实非常有用,尤其是那些抓住意图的注释。此外,这样的注释往往需要较少的调整,因为代码意图不会经常更改(如果有的话)。
# 测试是获得高质量代码的最短路径
......
......@@ -86,7 +86,7 @@
9. a、b、c
10. c、d(注意使用`mkdir()`方法代替`mkdirs()`
# 第 6 章 数据结构、泛型和流行实用程序
# 第 6 章 数据结构、泛型和流行工具
1. d
2. b、d
......@@ -287,7 +287,7 @@
1. b、c、d
2. 将对 JMH 的依赖添加到项目中(如果手动运行,则添加类路径),并将注释`@Benchmark`添加到要测试性能的方法中
3. 作为主方法使用带有显式命名的主类的 Java 命令,作为主方法使用带有可执行的`.jar`文件的 Java 命令,并且使用 IDE 运行作为主方法或者使用插件并运行单个方法
3. 作为`main`方法使用带有显式命名的主类的 Java 命令,作为`main`方法使用带有可执行的`.jar`文件的 Java 命令,并且使用 IDE 运行作为`main`方法或者使用插件并运行单个方法
4. 以下任意两项:`Mode.AverageTime``Mode.Throughput``Mode.SampleTime``Mode.SingleShotTime`
5. 以下任意两项:`TimeUnit.NANOSECONDS``TimeUnit.MICROSECONDS``TimeUnit.MILLISECONDS``TimeUnit.SECONDS``TimeUnit.MINUTES``TimeUnit.HOURS``TimeUnit.DAYS`
......
......@@ -2,7 +2,7 @@
本书的第二部分构成了 Java 演示的主要部分。它讨论了主要的 Java 组件和结构,以及算法和数据结构。详细回顾了 Java 的异常系统,还介绍了字符串类和 I/O 流,以及允许管理文件的类。
本文讨论并演示了 Java 集合和三个主要接口——`List``Set``Map`——并解释了泛型,接着介绍了用于管理数组、对象和时间/日期值的实用程序类。这些类属于 **Java 类库****JCL**),我们也讨论了其中最流行的包。第三方库在编程专业人士中很受欢迎,对它们进行了补充。
本文讨论并演示了 Java 集合和三个主要接口——`List``Set``Map`——并解释了泛型,接着介绍了用于管理数组、对象和时间/日期值的工具类。这些类属于 **Java 类库****JCL**),我们也讨论了其中最流行的包。第三方库在编程专业人士中很受欢迎,对它们进行了补充。
所提供的资料引发了对编程方面的讨论,如性能、并发处理和垃圾收集,这些都是 Java 设计的核心。它与有关图形用户界面和数据库管理的专门章节一起,涵盖了所有强大 Java 应用的所有三个层次:前端、中间和后端。有关网络协议和应用相互通信方式的一章完整地描述了应用可以进行的所有主要交互。
......@@ -12,7 +12,7 @@
第 5 章、“字符串、输入/输出和文件”
第 6 章、“数据结构、泛型和流行实用程序
第 6 章、“数据结构、泛型和流行工具
第 7 章“Java 标准和外部库”
......
......@@ -40,7 +40,7 @@ Java11 及其新特性增加了语言的丰富性,这是构建健壮软件应
第 14 章“命令行标志”,探讨了现代 Java 平台的一些变化,这些变化的共同主题是命令行标志。这些包括以下概念:统一 JVM 日志、编译器控制、诊断命令、堆分析代理、删除 JHAT、命令行标志参数验证、针对旧平台版本的编译,以及实验性的基于 Java 的 JIT 编译器。
第 15 章“Java 平台的附加增强”,重点介绍 Java 平台提供的附加实用程序的最佳实践。具体来说,本章介绍对 UTF-8、Unicode 支持、Linux/AArch64 端口、多分辨率图像和公共区域设置数据存储库的支持。
第 15 章“Java 平台的附加增强”,重点介绍 Java 平台提供的附加工具的最佳实践。具体来说,本章介绍对 UTF-8、Unicode 支持、Linux/AArch64 端口、多分辨率图像和公共区域设置数据存储库的支持。
第 16 章“未来方向”概述了 Java 平台在 Java11 之外的未来发展。我们看一下 Java19.3 和 19.9 的计划内容,以及将来可能会看到哪些进一步的变化。我们首先简要介绍一下 **JDK 增强程序****JEP**)。
......
......@@ -68,7 +68,7 @@ Java9 于 2017 年发布,2018 年计划发布两个版本。这些版本是 Ja
# 拆解整体
多年来,Java 平台的实用程序不断发展和增加,使其成为一个巨大的整体。为了使平台更适合于嵌入式和移动设备,有必要发布精简版,如 Java **连接设备配置****CDC**)和 Java **微型版****ME**)。然而,对于 JDK 所提供的功能有不同需求的现代应用来说,这些方法并没有足够的灵活性。在这方面,对模块化系统的需求是一个至关重要的需求,不仅是为了解决 Java 实用程序的模块化(总的来说,HotSpot 运行时有 5000 多个 Java 类和 1500 多个 C++ 源文件,其中包含 250000 多行代码),而且还为开发人员提供了一种创建和管理的机制使用与 JDK 中使用的模块系统相同的模块化应用。Java8 提供了一种中间机制,使应用能够只使用整个 JDK 提供的 API 的一个子集,这种机制被命名为**紧凑概要文件**。事实上,紧凑的概要文件还为进一步的工作提供了基础,这些工作是为了打破 JDK 不同组件之间的依赖关系。为了在 Java 中实现模块系统,需要打破依赖关系。
多年来,Java 平台的工具不断发展和增加,使其成为一个巨大的整体。为了使平台更适合于嵌入式和移动设备,有必要发布精简版,如 Java **连接设备配置****CDC**)和 Java **微型版****ME**)。然而,对于 JDK 所提供的功能有不同需求的现代应用来说,这些方法并没有足够的灵活性。在这方面,对模块化系统的需求是一个至关重要的需求,不仅是为了解决 Java 工具的模块化(总的来说,HotSpot 运行时有 5000 多个 Java 类和 1500 多个 C++ 源文件,其中包含 250000 多行代码),而且还为开发人员提供了一种创建和管理的机制使用与 JDK 中使用的模块系统相同的模块化应用。Java8 提供了一种中间机制,使应用能够只使用整个 JDK 提供的 API 的一个子集,这种机制被命名为**紧凑概要文件**。事实上,紧凑的概要文件还为进一步的工作提供了基础,这些工作是为了打破 JDK 不同组件之间的依赖关系。为了在 Java 中实现模块系统,需要打破依赖关系。
模块系统本身以 Jigsaw 项目的名义开发,在此基础上形成了多个 Java 增强方案和一个目标 **Java 规范请求****JSR376**)。对 JDK 代码库进行了完整的重组,同时对 JDK 可分发映像进行了完整的重组。
......@@ -111,7 +111,7 @@ G1 垃圾收集器已经在 JDK7 中引入,现在在 JDK9 中默认启用。
# 为 HTTP 2.0 做准备
HTTP2.0 是 HTTP1.1 协议的继承者,这个新版本的协议解决了前一个协议的一些限制和缺点。HTTP 2.0 以多种方式提高性能,并提供诸如在单个 TCP 连接中请求/响应多路复用、在服务器推送中发送响应、流控制和请求优先级等功能。Java 提供了可用于建立不安全 HTTP 1.1 连接的`java.net.HttpURLConnection`实用程序。然而,API 被认为难以维护,这一问题由于需要支持 HTTP 2.0 而变得更加复杂,因此引入了全新的客户端 API,以便通过 HTTP 2.0 或 Web 套接字协议建立连接。HTTP 2.0 客户端及其提供的功能,将在第 11 章、“新工具和工具增强”中介绍。
HTTP2.0 是 HTTP1.1 协议的继承者,这个新版本的协议解决了前一个协议的一些限制和缺点。HTTP 2.0 以多种方式提高性能,并提供诸如在单个 TCP 连接中请求/响应多路复用、在服务器推送中发送响应、流控制和请求优先级等功能。Java 提供了可用于建立不安全 HTTP 1.1 连接的`java.net.HttpURLConnection`工具。然而,API 被认为难以维护,这一问题由于需要支持 HTTP 2.0 而变得更加复杂,因此引入了全新的客户端 API,以便通过 HTTP 2.0 或 Web 套接字协议建立连接。HTTP 2.0 客户端及其提供的功能,将在第 11 章、“新工具和工具增强”中介绍。
# 包含反应式编程
......
......@@ -98,7 +98,7 @@ class Sample {
使用 AtoMIC 工具箱的关键是理解可变变量。可变变量、字段和数组元素可以由并发线程异步修改。
在 Java 中,`volatile`关键字用于通知 Javac 实用程序从主存中读取值、字段或数组元素,而不是缓存它们。
在 Java 中,`volatile`关键字用于通知 Javac 工具从主存中读取值、字段或数组元素,而不是缓存它们。
下面是一段代码片段,演示了对实例变量使用`volatile`关键字:
......@@ -164,8 +164,8 @@ Java9 略微减少了我们收到的警告数量。特别是,不再生成由
现在,如果以下一种或多种情况为真,编译器将抑制废弃警告:
* 如果使用`@Deprecated`
* 如果使用`@SuppressWarnings`
* 如果使用`@Deprecated`
* 如果使用`@SuppressWarnings`
* 如果警告生成代码和声明在祖先类中使用
* 如果警告生成代码在`import`语句中使用
......@@ -186,9 +186,9 @@ Coin 项目是 Java7 中引入的一组小改动的特性集。这些变化如
对于 Java9 版本,Coin 项目有五个改进。这些增强功能将在下面的部分中详细介绍。
# 使用`@SafeVarargs`注
# 使用`@SafeVarargs`注
从 Java9 开始,我们可以将`@SafeVarargs`与私有实例方法结合使用。当我们使用这个注释时,我们断言这个方法不包含对作为参数传递给这个方法的`varargs`的任何有害操作。
从 Java9 开始,我们可以将`@SafeVarargs`与私有实例方法结合使用。当我们使用这个注释时,我们断言这个方法不包含对作为参数传递给这个方法的`varargs`的任何有害操作。
使用的语法如下:
......@@ -204,7 +204,7 @@ static void methodName(...) {
}
```
`@SafeVarargs`的使用仅限于以下内容:
`@SafeVarargs`的使用仅限于以下内容:
* 静态方法
* 最终方法
......@@ -467,7 +467,7 @@ public interface Inner {
1. `SamplePackage.OuterPackage`的解析开始。
2. 处理`SamplePackage.OuterPackage.Nested`导入。
3. `SamplePackage.Outer.Nested`的决议开始。
3. `SamplePackage.Outer.Nested`的决议开始。
4. 内部接口是经过类型检查的,但由于此时它不在范围内,因此无法解析内部接口。
5. `SamplePackage.Thing`的解析开始。此步骤包括将`SamplePackage.Thing`的所有成员类型导入范围。
......
......@@ -143,7 +143,7 @@ JEP-200 的核心目标是使用 **Java 平台模块系统**(**JPMS**)对 JD
这三个紧凑的模块概要文件代表了当前 Java 平台中标准化模块化系统的基础。标准化的有效性取决于以下六个原则:
* 所有 JCP 管理的模块都必须以字符串`java`开头。因此,如果正在开发一个关于空间实用程序的模块,它的名称应该是`java.spatial.util`
* 所有 JCP 管理的模块都必须以字符串`java`开头。因此,如果正在开发一个关于空间工具的模块,它的名称应该是`java.spatial.util`
**JCP** 是指 **Java 社区流程**。 JCP 允许开发人员为 Java 创建技术规范。 您可以在 [JCP 官方网站](https://www.jcp.org/en/home/index)上了解有关 JCP 的更多信息并成为会员 
......@@ -380,7 +380,7 @@ JRE 和 JDK 映像之间不再有区别。在当前的 Java 平台上,JDK 映
# 保留现有行为
JEP-220 的最终目标是确保现有的课程不会受到负面影响。这是指不依赖于内部 JDK 或 JRE 运行时映像的应用。
JEP-220 的最终目标是确保现有的不会受到负面影响。这是指不依赖于内部 JDK 或 JRE 运行时映像的应用。
# 模块系统
......
......@@ -268,7 +268,7 @@ public class GeneratePassword {
}
```
在下面的屏幕截图中,我们在运行 Java8 的 Mac 上测试了`GeneratePassword`应用。如您所见,我们首先查询 Java 以验证当前版本。在这个测试中,使用了 Java`1.8.0_121`。接下来,我们使用`javac`实用程序编译`GeneratePassword`Java 文件。最后,我们运行应用:
在下面的屏幕截图中,我们在运行 Java8 的 Mac 上测试了`GeneratePassword`应用。如您所见,我们首先查询 Java 以验证当前版本。在这个测试中,使用了 Java`1.8.0_121`。接下来,我们使用`javac`工具编译`GeneratePassword`Java 文件。最后,我们运行应用:
![](img/449e6f07-52e1-4e66-88a4-16153d77be70.png)
......@@ -583,7 +583,7 @@ public class DependencyTest {
# `--add-opens`选项
您可以使用`--add-opens`运行时选项来允许您的代码访问非公共成员。这可以称为**深反射**。进行这种深度反思的图书馆能够访问所有成员,包括私和公共。要授予这种类型的代码访问权限,可以使用`--add-opens`选项。语法如下:
您可以使用`--add-opens`运行时选项来允许您的代码访问非公共成员。这可以称为**深反射**。进行这种深度反思的图书馆能够访问所有成员,包括私和公共。要授予这种类型的代码访问权限,可以使用`--add-opens`选项。语法如下:
```java
--add-opens <module>/<package>=<target-module>(,<target-module>)*
......@@ -940,7 +940,7 @@ Maven 可以与 Eclipse(M2Eclipse)、JetBrains IntelliJ IDEA 和 netbeansIDE
在本章中,我们探讨了将现有应用迁移到当前 Java 平台时可能涉及的问题。我们研究了手动和半自动迁移过程,本章为您提供了一些见解和过程,使您的 Java8 代码能够在新的 Java 平台上工作。具体来说,我们对项目 Jigsaw 进行了快速回顾,研究了模块如何适应 Java 环境,提供了迁移规划的技巧,共享了 Oracle 关于迁移的建议,以及可以在开始时使用的共享工具。
在下一章中,我们将详细介绍 JavaShell 和 JShellAPI。我们将演示 JShellAPI 和 JShell 工具以交互方式评估 Java 编程语言的声明、语句和表达式的能力。我们将演示此命令行工具的特性和用法。
在下一章中,我们将详细介绍 JavaShell 和 JShellAPI。我们将演示 JShellAPI 和 JShell 工具以交互方式求值 Java 编程语言的声明、语句和表达式的能力。我们将演示此命令行工具的特性和用法。
# 问题
......
......@@ -21,7 +21,7 @@ IDE 软件包就足够了。来自 JetBrains 的 IntelliJ IDEA 用于与本章
# 了解 JShell
**JShell** 是 Java 平台上比较新的一个重要工具。它是在 JDK9 中引入的。它是一个交互式 REPL 工具,用于评估以下 Java 编程语言组件声明、语句和表达式。它有自己的 API,因此可以被外部应用使用。
**JShell** 是 Java 平台上比较新的一个重要工具。它是在 JDK9 中引入的。它是一个交互式 REPL 工具,用于求值以下 Java 编程语言组件声明、语句和表达式。它有自己的 API,因此可以被外部应用使用。
**读取求值打印循环** 通常称为 **REPL**,取自词组中每个单词的第一个字母。 它也被称为语言外壳或交互式顶层。
......@@ -64,7 +64,7 @@ jshell <options> <load files>
| --- | --- |
| `/drop` | 使用此命令删除被`name``id`引用的源条目。语法如下:`/drop <name or id>` |
| `/edit` | 使用此命令,您可以使用`name``id`引用编辑源条目语法如下:`/edit <name or id>` |
| `/env` | 这个强大的命令允许您查看或更改评估上下文语法如下:`/env [-class-path <path>]  [-module-path <path>]  [-add-modules <modules>]` |
| `/env` | 这个强大的命令允许您查看或更改求值上下文语法如下:`/env [-class-path <path>]  [-module-path <path>]  [-add-modules <modules>]` |
| `/exit` | 此命令用于退出 JShell。语法是简单的`/exit`,没有任何可用的选项或参数。 |
| `/history` | `history`命令提供您所键入内容的历史记录。语法是简单的`/history`,没有任何可用的选项或参数。 |
| `/<id>` | 此命令用于通过引用`id`重新运行以前的代码段。语法如下:`/<id>`您也可以使用`/-<n>`引用前`n`个代码段来运行特定的代码段。 |
......
......@@ -292,13 +292,13 @@ public class GCVerificationTest {
你可以把 Java 的垃圾收集器想象成死亡贩子。当它从记忆中删除某些东西时,它就消失了。这个所谓的死亡贩子并非没有同情心,因为它为每个方法提供了他们最后的遗言。对象通过`finalize()`方法给出他们的最后一句话。如果一个对象有一个`finalize()`方法,垃圾收集器会在移除该对象和释放相关内存之前调用它。该方法不带参数,返回类型为`void`
`finalize()`方法只调用一次,在运行时可能会有变化,当然,方法是在被删除之前调用的,但是垃圾收集器运行时依赖于系统。例如,如果您有一个运行内存丰富系统的相对较小的应用,则垃圾收集器可能根本不会运行。那么,为什么要包含一个`finalize()`方法呢?重写`finalize()`方法被认为是糟糕的编程实践。也就是说,如果需要的话,你可以使用这个方法。实际上,您可以在那里添加代码来添加对对象的引用,以确保垃圾收集器不会删除该对象。同样,这是不可取的。
`finalize()`方法只调用一次,在运行时可能会有变化,当然,方法是在被删除之前调用的,但是垃圾收集器运行时依赖于系统。例如,如果您有一个运行内存丰富系统的相对较小的应用,则垃圾收集器可能根本不会运行。那么,为什么要包含一个`finalize()`方法呢?覆盖`finalize()`方法被认为是糟糕的编程实践。也就是说,如果需要的话,你可以使用这个方法。实际上,您可以在那里添加代码来添加对对象的引用,以确保垃圾收集器不会删除该对象。同样,这是不可取的。
因为 Java 中的所有对象,甚至是您自己创建的对象,都是`java.lang.Object`的子类,所以 Java 中的每个对象都有一个`finalize()`方法
垃圾收集器虽然复杂,但可能无法按您希望的方式关闭数据库、文件或网络连接。如果您的应用在收集其对象时需要特定的注意事项,您可以重写对象的`finalize()`方法
垃圾收集器虽然复杂,但可能无法按您希望的方式关闭数据库、文件或网络连接。如果您的应用在收集其对象时需要特定的注意事项,您可以覆盖对象的`finalize()`方法
下面是一个示例实现,它演示了当您可能希望重写对象的`finalize()`方法时的一个用例:
下面是一个示例实现,它演示了当您可能希望覆盖对象的`finalize()`方法时的一个用例:
```java
public class Animal {
......@@ -340,7 +340,7 @@ public class Animal {
正如您在前面的代码中所看到的,`objectTally`计数在每次创建类型为`Animal`的对象时递增,而在垃圾收集器删除类型为`Animal`的对象时递减。
通常不鼓励重写对象的`finalize()`方法。`finalize()`方法通常应声明为`protected`
通常不鼓励覆盖对象的`finalize()`方法。`finalize()`方法通常应声明为`protected`
# Java9 之前的垃圾收集模式
......@@ -627,7 +627,7 @@ Java9 日志框架支持三种类型的输出:
| 使用此选项,可以将垃圾收集输出重定向到文件而不是控制台。 | `-Xloggc:` |
| 您可以在每个收集周期之后打印有关年轻空间的详细信息。 | `-XX:+Print\TenuringDistribution` |
| 可以使用此标志打印 TLAB 分配统计信息。 | `-XX:+PrintTLAB` |
| 使用此标志,您可以打印`S`top the World*暂停期间的参考处理时间(即弱、软等)。 | `-XX:+PrintReferenceGC` |
| 使用此标志,您可以打印`Stop the World`暂停期间的参考处理时间(即弱、软等)。 | `-XX:+PrintReferenceGC` |
| 此报告垃圾收集是否正在等待本机代码取消固定内存中的对象。 | `-XX:+PrintJNIGCStalls` |
| 每次*停止*暂停后,打印暂停摘要。 | `-XX:+PrintGC\ApplicationStoppedTime` |
| 此标志将打印垃圾收集的每个并发阶段的时间。 | `-XX:+PrintGC\ApplicationConcurrentTime` |
......
......@@ -374,7 +374,7 @@ int newValue = 3190;
有两种策略可以解决这个陷阱:
* 使用不同的问题集运行多个基准测试。在测试期间跟踪内存占用。
* 使用`@State`来指示 JMH 状态。此注释用于定义实例的范围。有三种状态:
* 使用`@State`来指示 JMH 状态。此注释用于定义实例的范围。有三种状态:
* `Scope.Benchmark`:实例在运行同一测试的所有线程之间共享
* `Scope.Group`:每个线程组分配一个实例
* `Scope.Thread`:每个线程都有自己的实例。这是默认状态
......
......@@ -238,7 +238,7 @@ public class ProcessLister {
这是因为某些操作系统没有实现优雅的进程终止特性。在这种情况下,`destroy()`的实现与调用`destroyForcefully()`相同。接口`ProcessHandle`的系统特定实现必须实现方法`supportsNormalTermination()`,只有当实现支持正常(非强制)进程终止时,才应该是`true`。对于实际实现中的所有调用,该方法应返回相同的值,并且在执行 JVM 实例期间不应更改返回值。不需要多次调用该方法。
下面的示例演示了进程启动、进程终止和等待进程终止。在我们的示例中,我们使用两个类。第一节课演示了`.sleep()`方法:
下面的示例演示了进程启动、进程终止和等待进程终止。在我们的示例中,我们使用两个类。第一个类演示了`.sleep()`方法:
```java
public class WaitForChildToBeTerminated {
......
......@@ -289,7 +289,7 @@ stackwalker/packt.Main.simpleCall(Main.java:12)
stackwalker/packt.Main.main(Main.java:8)
```
此输出仅包含属于代码中的调用的帧。`main()`方法调用`simpleCall()`,后者调用`reflectCall()`,后者依次调用`lambdaCall()`,后者调用 Lambda 表达式,后者调用`walk()`,依此类推。我们没有指定任何选项的事实并没有从栈中删除 Lambda 调用。我们执行了那个呼叫,所以它一定在那里。它删除的是 JVM 实现 Lambda 所需的额外栈帧。我们可以在下一个输出中看到,当选项为`SHOW_REFLECT_FRAMES`时,反射帧已经存在:
此输出仅包含属于代码中的调用的帧。`main()`方法调用`simpleCall()`,后者调用`reflectCall()`,后者依次调用`lambdaCall()`,后者调用 Lambda 表达式,后者调用`walk()`,依此类推。我们没有指定任何选项的事实并没有从栈中删除 Lambda 调用。我们执行了那个调用,所以它一定在那里。它删除的是 JVM 实现 Lambda 所需的额外栈帧。我们可以在下一个输出中看到,当选项为`SHOW_REFLECT_FRAMES`时,反射帧已经存在:
```java
stackwalker/packt.Main.reflect(Main.java:58)
......@@ -371,7 +371,7 @@ public static void itIsNotCallBack() {
}
```
我们所做的只是直接从遍历器调用返回流,然后遍历流,然后执行相同的计算。我们的结果是`IllegalStateException`例外,而不是资格检查。
我们所做的只是直接从遍历器调用返回流,然后遍历流,然后执行相同的计算。我们的结果是`IllegalStateException`异常,而不是资格检查。
原因是`StackWalker`的实现高度优化。它不会复制整个栈来为流提供源信息。它是从实际的,活生生的栈中工作的。为此,必须确保在使用流时不修改栈。这与迭代集合时更改集合可能得到的`ConcurrentModificationException`异常非常相似。如果我们在调用栈中向上传递流,然后想要从中获取`StackFrame`,那么流将尝试从早已消失的栈帧中获取信息,因为我们从它所属的方法返回。这样,`StackWalker`就不会生成整个栈的快照,而是从实际栈开始工作,并且必须确保所需的栈部分不会更改。我们可以从函数中调用方法,这样我们可以在调用链中更深入地挖掘,但是在流被使用时,我们不能得到更高的值。
......
......@@ -119,7 +119,7 @@ JDK1.1 版引入了支持 HTTP 特定特性的`HttpURLConnection`API。这是一
为现代 Java 平台创建新的 HTTP 客户端有几个相关的目标,java9、10 和 11 提供了这些目标。下表列出了主要目标。这些目标分为易用性、核心功能、附加功能和性能等大类:
| 易用性 | API 旨在提供高达 90% 的 HTTP 相关应用程序要求。 |
| 易用性 | API 旨在提供高达 90% 的 HTTP 相关应用要求。 |
| --- | --- |
| | 对于最常见的用例,新 API 是可用的,没有不必要的复杂性。 |
| | 包括一个简单的阻塞模式。 |
......@@ -349,7 +349,7 @@ Doclet API 包含以下列出的接口。接口名称是不言自明的。有关
* 它不适合于测试或并发使用。这源于它对静态方法的实现。
* API 中使用的语言模型有几个限制,并且随着每次 Java 升级而变得更麻烦。
* API 效率低下,主要是因为它大量使用子字符串匹配。
* 没有提及任何评论的具体位置。这使得诊断和故障排除变得困难。
* 没有提及任何注释的具体位置。这使得诊断和故障排除变得困难。
# Java9 的 Doclet API
......@@ -457,7 +457,7 @@ Doclet API 包含以下列出的接口。接口名称是不言自明的。有关
* `AnnotatedConstruct`接口
* `SourceVersion`枚举
* `UnknownEntityException`例外
* `UnknownEntityException`异常
下面三节将进一步探讨这些语言模型 API 组件中的每一个。
......@@ -1005,7 +1005,7 @@ public class MyBean implements java.io.Serializable {
`BeanProperty`是注释类型。我们使用这个注释来指定一个属性,这样我们就可以自动生成`BeanInfo`类。这是一个相对较新的 Java 注释,从 Java9 开始
`BeanProperty`具有以下可选元素:
`BeanProperty`具有以下可选元素:
* `boolean bound`
* `String description`
......@@ -1020,7 +1020,7 @@ public class MyBean implements java.io.Serializable {
`SwingContainer`是注释类型。我们使用这个注释来指定与 Swing 相关的属性,这样我们就可以自动生成`BeanInfo`类。
`SwingContainer`具有以下可选元素:
`SwingContainer`具有以下可选元素:
* `String delegate`
* `boolean value`
......@@ -1029,7 +1029,7 @@ public class MyBean implements java.io.Serializable {
# `BeanInfo`类
在大多数情况下,`BeanInfo`类是在运行时自动生成的。例外是`Swing`类。这些类基于`@beaninfo`Javadoc 标记生成`BeanInfo`类。这是在编译时完成的,而不是在运行时。从 Java9 开始,`@beaninfo`标记被`@interface JavaBean``@interface BeanProperty``@interface SwingContainer`所取代。
在大多数情况下,`BeanInfo`类是在运行时自动生成的。例外是`Swing`类。这些类基于`@beaninfo`Javadoc 标记生成`BeanInfo`类。这是在编译时完成的,而不是在运行时。从 Java9 开始,`@beaninfo`标记被`@interface JavaBean``@interface BeanProperty``@interface SwingContainer`所取代。
这些新注释用于根据前面部分中提到的可选元素设置相应的属性。例如,下面的代码片段设置了`SwingContainer`的属性:
......@@ -1127,7 +1127,7 @@ public @interface SwingContainer {
* 方法:`isLoggable(LogRecord record)`
* `public interface LoggingMXBean`
* 用途:这是日志设备的管理界面
* 用途:这是日志设备的管理接口
* 方法:
* `getLoggerLevel(String loggerName)`
* `getLoggerNames()`
......@@ -1321,7 +1321,7 @@ public class OldSchool {
![](img/8d2afd93-4226-4018-a9b1-d009b4bd9362.png)
旧学校班级产
`OldSchool`类的输
不幸的是,这段代码非常冗长。我们在静态初始化器块中填充集合,而不是使用字段初始化器。还有其他方法填充我们的列表,但它们都比应该的更冗长。这些其他方法还有其他问题,比如需要创建额外的类、使用晦涩的代码和隐藏的引用。
......@@ -1386,9 +1386,9 @@ MacOSX`com.apple.eawt`包是一个内部 API,从 Java9 开始,就不能再
现代 Java 平台包括增强的方法句柄,作为改进以下列出的类的一种方法,以便通过改进的优化简化常见用法:
* `MethodHandle`
* `MethodHandles`
* `MethodHandles.Lookup`
* `MethodHandle`
* `MethodHandles`
* `MethodHandles.Lookup`
前面的类都是`java.lang.invoke`包的一部分,该包已针对现代 Java 平台进行了更新。这些改进是通过使用`MethodHandle`组合、`for`循环和`try...finally`块的查找细化实现的。
......@@ -1466,11 +1466,11 @@ MacOSX`com.apple.eawt`包是一个内部 API,从 Java9 开始,就不能再
* `@Deprecated`注解
* `@deprecated`Javadoc 标签
这些工具分别在 JavaSE5 和 JDK1.1 中引入。`@Deprecated`的目的是注释那些不应该使用的程序组件,因为它们被认为是危险的和/或有更好的选择。这就是预期用途,实际用途各不相同,而且由于警告只在编译时提供,因此几乎没有理由忽略带注释的代码。
这些工具分别在 JavaSE5 和 JDK1.1 中引入。`@Deprecated`的目的是注释那些不应该使用的程序组件,因为它们被认为是危险的和/或有更好的选择。这就是预期用途,实际用途各不相同,而且由于警告只在编译时提供,因此几乎没有理由忽略带注释的代码。
增强的弃用工作是为了向开发人员提供关于规范文档中 API 的预期配置的更清晰的信息。这方面的工作还产生了一个分析程序使用不推荐的 API 的工具。
为了支持信息的保真度,以下组件被添加到`java.lang.Deprecated`类型中:
为了支持信息的保真度,以下组件被添加到`java.lang.Deprecated`类型中:
* `forRemoval()`
* 返回布尔值`true`,如果 API 元素已被安排在将来删除
......@@ -1482,7 +1482,7 @@ MacOSX`com.apple.eawt`包是一个内部 API,从 Java9 开始,就不能再
# `@Deprecated`注解的真正含义
当一个 API 或 API 中的方法已标记有`@Deprecated`时,通常存在以下一个或多个条件:
当一个 API 或 API 中的方法已标记有`@Deprecated`时,通常存在以下一个或多个条件:
* API 中存在错误,没有计划修复这些错误
* 使用 API 可能会导致错误
......
......@@ -246,7 +246,7 @@ Java 平台最近得到了增强,以改进并发性的使用。在本节中,
# Java 线程
Java 中的线程是一个程序执行,内置在 JVM 中。`Thread`类是`java.lang`包(`java.lang.Thread`的一部分。线程具有控制 JVM 执行它们的顺序的优先级。虽然概念很简单,但实现却不简单。让我们先来仔细看看`Thread`课程
Java 中的线程是一个程序执行,内置在 JVM 中。`Thread`类是`java.lang`包(`java.lang.Thread`的一部分。线程具有控制 JVM 执行它们的顺序的优先级。虽然概念很简单,但实现却不简单。让我们先来仔细看看`Thread`
`Thread`类有一个嵌套类:
......@@ -352,7 +352,7 @@ public class unprotectedMethod() {
在我们的 Java 应用中使用多线程的能力将极大地提高效率,并利用现代计算机日益增长的处理能力。Java 中线程的使用为我们的并发控制提供了很大的粒度。
线程是 Java 并发功能的核心。我们可以通过定义一个`run`方法并实例化一个`Thread`对象,在 Java 中创建一个线程。完成这组任务有两种方法。我们的第一个选择是扩展`Thread`类并重写`Thread.run()`方法。下面是这种方法的一个例子:
线程是 Java 并发功能的核心。我们可以通过定义一个`run`方法并实例化一个`Thread`对象,在 Java 中创建一个线程。完成这组任务有两种方法。我们的第一个选择是扩展`Thread`类并覆盖`Thread.run()`方法。下面是这种方法的一个例子:
```java
. . .
......
......@@ -124,7 +124,7 @@ struct
本节中的代码来自 DTLS 协议文件,并根据 IETF *有关文件*的法律规定重新发布。
记录层包含我们打算发送到记录中的信息。信息开始于`DTLSPlaintext`结构中,然后在握手发生之后,记录被加密,并且可以通过通信流发送。记录层格式遵循 1.2 版中的新字段,并在代码注释中用`// New field`,如下所示:
记录层包含我们打算发送到记录中的信息。信息开始于`DTLSPlaintext`结构中,然后在握手发生之后,记录被加密,并且可以通过通信流发送。记录层格式遵循 1.2 版中的新字段,并在代码注释中用`// New field`,如下所示:
```java
// Copyright (c) 2012 IETF Trust and the persons identified
......@@ -474,7 +474,7 @@ public class CodeSource extends Object implements Serializable
此类具有以下方法:
* `public boolean equals(Object obj)`:如果对象相等,则返回`true`。这将重写`Object`类中的`equals`方法。
* `public boolean equals(Object obj)`:如果对象相等,则返回`true`。这将覆盖`Object`类中的`equals`方法。
* `public final Certificate[] getCertificates()`:返回证书数组。
* `public final CodeSigner[] getCodeSigners()`:返回与`CodeSource`关联的代码签名者数组。
* `public final URL getLocation()`:返回 URL。
......@@ -673,10 +673,10 @@ X.509 证书是使用 X509 **公钥基础设施**(**PKI**)的数字证书。
* `jdk.tls.stapling.responderURI`
* 默认设置:无
* 可以为没有**权限信息访问****AIA**)扩展的证书设置默认 URI
* 除非设置了`jdk.tls.stapling.Override`属性,否则不重写 AIA 扩展
* 除非设置了`jdk.tls.stapling.Override`属性,否则不覆盖 AIA 扩展
* `jdk.tls.stapling.respoderOverride`
* 默认设置:`false`
* 允许`jdk.tls.stapling.responderURI`提供的属性重写 AIA 扩展值
* 允许`jdk.tls.stapling.responderURI`提供的属性覆盖 AIA 扩展值
* `jdk.tls.stapling.ignoreExtensions`
* 默认设置:`false`
* 禁用 OCSP 扩展转发,如`status_request``status_request_v2`TLS 扩展中所述
......
......@@ -458,7 +458,7 @@ javac --release <release> <source files>
在本章中,我们探讨了现代 Java 平台的一些变化,这些变化的共同主题是命令行标志。具体来说,我们讨论了统一 JVM 日志记录、编译器控制、新的诊断命令、HPROF 堆分析代理的删除、JHAT 的删除、命令行标志参数验证,以及针对旧平台版本进行编译的能力。
在下一章中,我们将重点介绍 Java 中提供的附加实用程序的最佳实践。其中包括 UTF-8、Unicode 7.0、Linux 等等。
在下一章中,我们将重点介绍 Java 中提供的附加工具的最佳实践。其中包括 UTF-8、Unicode 7.0、Linux 等等。
# 问题
......
......@@ -2,7 +2,7 @@
在最后一章中,我们探讨了 Java 中命令行标志的一些变化,具体包括统一 JVM 日志记录、编译器控制、新的诊断命令、删除 HPROF 堆分析代理、删除 **Java 堆分析工具****JHAT**),命令行标志参数验证,以及为旧平台版本编译的能力。
在本章中,我们将重点介绍 Java 平台提供的附加实用程序的最佳实践。具体来说,我们将讨论以下主题:
在本章中,我们将重点介绍 Java 平台提供的附加工具的最佳实践。具体来说,我们将讨论以下主题:
* 支持 UTF-8
* Unicode 支持
......@@ -602,7 +602,7 @@ public final class String extends Object implements
# `java.text`包
`Bidi``BreakIterator``Normalizer`类的应用不如`Character``String`类广泛。以下是这些课程的简要概述:
`Bidi``BreakIterator``Normalizer`类的应用不如`Character``String`类广泛。以下是这些的简要概述:
这是`Bidi`类:
......@@ -730,7 +730,7 @@ java.locale.providers=SPI,COMPAT,CLDR
# 总结
在本章中,我们将重点介绍当前 Java 平台提供的附加实用程序的最佳实践。具体来说,我们介绍了 UTF-8 属性文件、Unicode 7.0.0、Linux/AArch64 端口、多分辨率图像和公共语言环境数据存储库。
在本章中,我们将重点介绍当前 Java 平台提供的附加工具的最佳实践。具体来说,我们介绍了 UTF-8 属性文件、Unicode 7.0.0、Linux/AArch64 端口、多分辨率图像和公共语言环境数据存储库。
在下一章中,我们将通过展望 Java19.3(Java12)和 Java19.9(Java13)中的内容来展望 Java 平台的未来方向。
......
# 未来发展方向
在最后一章中,我们重点介绍了 Java 平台提供的一些激动人心的实用程序的最佳实践。具体来说,我们介绍了 UTF-8 属性文件、Unicode、Linux/AArch64 端口、多分辨率图像和公共区域设置数据存储库。
在最后一章中,我们重点介绍了 Java 平台提供的一些激动人心的工具的最佳实践。具体来说,我们介绍了 UTF-8 属性文件、Unicode、Linux/AArch64 端口、多分辨率图像和公共区域设置数据存储库。
本章概述了 Java 平台在 Java11 之外的未来发展。我们将看看 Java19.3(12)和 19.9(13)的计划以及将来可能看到的进一步变化。我们将从一个简短的 JEP 概述开始。
......
......@@ -42,14 +42,14 @@ Java 社区由数以百万计的开发人员组成,他们以一种或多种方
# 参与 Java 用户组
**Java 用户组**也被称为 **JUGs**,由具有社区意识的 Java 专业人士组成,旨在分享他们的 Java 知识。有超过 200 个水壶,参与是自愿的。与其他专业用户群一样,JUGs 提供以下机会:
**Java 用户组**也被称为 **JUGs**,由具有社区意识的 Java 专业人士组成,旨在分享他们的 Java 知识。有超过 200 个用户组,参与是自愿的。与其他专业用户群一样,JUGs 提供以下机会:
* 与其他 Java 专业人士联网
* 分享技巧、技巧和资源
* 向他人学习
* 增加 Java 知识
水壶遍布全球。有些是按国家组织的,有些是按城市组织的。当你探索你所在地理区域的水壶时,你可能会发现他们有定期的见面会
用户组遍布全球。有些是按国家组织的,有些是按城市组织的。当你探索你所在地理区域的用户组时,你可能会发现他们有定期的见面会
# Java 社区流程
......
......@@ -30,7 +30,7 @@
1. 在 Java 中,栅栏操作是`javac`以屏障指令的形式对内存进行强制约束的操作。这些操作发生在屏障指令之前和之后,本质上是将它们封闭起来。
2. Coin 项目是 Java7 中引入的一组小改动的特性集。
3. 从 Java9 开始,我们可以对私有实例方法使用`@SafeVarargs`
3. 从 Java9 开始,我们可以对私有实例方法使用`@SafeVarargs`
4. 从 Java9 开始,我们在类和文件中列出`import`语句的顺序将不再影响编译过程。
5. Java 平台在`cacerts`密钥库中包含一组根证书。
6. `var`标识符在技术上是一个保留的类型名。
......@@ -87,7 +87,7 @@
# 第六章
1. 读取求值打印循环通常称为 REPL,从短语中的每个单词中提取第一个字母。它也被称为语言 Shell 或交互式顶层。
2. 它是一个交互式读取求值打印循环工具,用于评估以下 Java 编程语言组件声明、语句和表达式。它有自己的 API,因此可以被外部应用使用。
2. 它是一个交互式读取求值打印循环工具,用于求值以下 Java 编程语言组件声明、语句和表达式。它有自己的 API,因此可以被外部应用使用。
3. 如下所示:
* 制表符补全
......