提交 8932b07a 编写于 作者: W wizardforcel

2021-04-20 17:08:11

上级 971c78e5
...@@ -80,7 +80,7 @@ public class Order { ...@@ -80,7 +80,7 @@ public class Order {
} }
``` ```
包的名称为`dtos`,代表**数据传输对象****DTO**的复数形式。DTO 是用于在不同组件(通常通过网络)之间传输数据的对象。由于另一方可以用任何语言实现,封送可以是 JSON、XML 或其他一些只能传递数据的格式。这些类没有真正的方法。dto 通常只有字段、setter 和 getter。 包的名称为`dtos`,代表**数据传输对象****DTO**的复数形式。DTO 是用于在不同组件(通常通过网络)之间传输数据的对象。由于另一方可以用任何语言实现,封送可以是 JSON、XML 或其他一些只能传递数据的格式。这些类没有真正的方法。dto 通常只有字段、setter 和 getter。
以下是包含订单中一个项目的类: 以下是包含订单中一个项目的类:
...@@ -153,7 +153,7 @@ public interface ConsistencyChecker { ...@@ -153,7 +153,7 @@ public interface ConsistencyChecker {
我们知道,在开发之初,我们将有很多一致性检查,并不是所有的订单都相关。我们希望避免为每个订单调用每个检查器。为此,我们实现了一些过滤。我们让产品指定他们需要什么类型的检查。这是一段产品信息,如尺寸或描述。为了适应这种情况,我们需要扩展`ProductInformation`类。 我们知道,在开发之初,我们将有很多一致性检查,并不是所有的订单都相关。我们希望避免为每个订单调用每个检查器。为此,我们实现了一些过滤。我们让产品指定他们需要什么类型的检查。这是一段产品信息,如尺寸或描述。为了适应这种情况,我们需要扩展`ProductInformation`类。
我们将创建每个`ConsistencyChecker`接口,将类实现为一个 springbean(用`@Component`注释进行注释),同时,我们将用一个注释对它们进行注释,该注释指定它们实现的检查类型。同时,`ProductInformation`被扩展,包含一组`Annotation`类对象,这些对象指定要调用哪些 checker。我们可以简单地列出 checker 类,而不是注释,但是这给了我们在配置产品和注释之间的映射时更多的自由。注释指定产品的性质,并对 checker 类进行注释。台灯是`PoweredDevice`类型,checker 类`NeedPowercord``@PoweredDevice`注释。如果有任何其他类型的产品也需要电源线,那么该类型的注释应该添加到`NeedPowercord`类中,我们的代码就可以工作了。既然我们开始深入研究注释和注释处理,我们就必须首先了解注释到底是什么。我们从第 3 章*优化排序代码专业人员*开始就已经使用了注释,但我们所知道的只是如何使用它们,如果不了解我们所做的事情,这通常是危险的。 我们将创建每个`ConsistencyChecker`接口,将类实现为一个 springbean(用`@Component`注释进行注释),同时,我们将用一个注释对它们进行注释,该注释指定它们实现的检查类型。同时,`ProductInformation`被扩展,包含一组`Annotation`类对象,这些对象指定要调用哪些 checker。我们可以简单地列出 checker 类,而不是注释,但是这给了我们在配置产品和注释之间的映射时更多的自由。注释指定产品的性质,并对 checker 类进行注释。台灯是`PoweredDevice`类型,checker 类`NeedPowercord``@PoweredDevice`注释。如果有任何其他类型的产品也需要电源线,那么该类型的注释应该添加到`NeedPowercord`类中,我们的代码就可以工作了。既然我们开始深入研究注释和注释处理,我们就必须首先了解注释到底是什么。我们从第 3 章“优化专业排序代码”开始就已经使用了注释,但我们所知道的只是如何使用它们,如果不了解我们所做的事情,这通常是危险的。
# 注释 # 注释
...@@ -215,7 +215,7 @@ public class NeedPowercord implements ConsistencyChecker { ...@@ -215,7 +215,7 @@ public class NeedPowercord implements ConsistencyChecker {
... ...
``` ```
如果参数的名称是一个值,并且在注释的使用位置没有定义其他参数,则可以跳过名称*值*。例如,当我们只有一个参数时,按以下方式修改代码是一种方便的速记: 如果参数的名称是一个值,并且在注释的使用位置没有定义其他参数,则可以跳过名称`value`。例如,当我们只有一个参数时,按以下方式修改代码是一种方便的速记:
```java ```java
public @interface ParameteredPoweredDevice{ public @interface ParameteredPoweredDevice{
...@@ -323,7 +323,7 @@ public class NeedPowercord implements ConsistencyChecker { ...@@ -323,7 +323,7 @@ public class NeedPowercord implements ConsistencyChecker {
# @文档注释 # @文档注释
`@Documented`注释表示注释是实体合同的一部分的意图,因此必须进入文档。这是一个注释,当为引用`@Documented`注释的元素创建文档时,*JavaDoc*生成器将查看该注释。 `@Documented`注释表示注释是实体合同的一部分的意图,因此必须进入文档。这是一个注释,当为引用`@Documented`注释的元素创建文档时,*JavaDoc* 生成器将查看该注释。
# JDK 注释 # JDK 注释
...@@ -677,7 +677,7 @@ private void setValueInChecker(ConsistencyChecker checker) { ...@@ -677,7 +677,7 @@ private void setValueInChecker(ConsistencyChecker checker) {
函数式编程不是什么新鲜事。它的数学背景是在 20 世纪 30 年代发展起来的,最早(如果不是最早)的函数式编程语言之一是 LISP。它是在 20 世纪 50 年代开发的,现在仍在使用,以至于有一个版本的语言在 JVM 上实现(Clojure)。 函数式编程不是什么新鲜事。它的数学背景是在 20 世纪 30 年代发展起来的,最早(如果不是最早)的函数式编程语言之一是 LISP。它是在 20 世纪 50 年代开发的,现在仍在使用,以至于有一个版本的语言在 JVM 上实现(Clojure)。
简而言之,函数式编程就是用函数来表示程序结构。从这个意义上说,我们应该把函数看作是数学中的函数,而不是编程语言(如 C)中使用的术语。在 Java 中,我们有方法,当我们遵循函数编程范式时,我们创建和使用的方法的行为类似于数学函数。如果一个方法无论调用多少次都给出相同的结果,那么它就是函数性的,就像*sin(0)*总是零一样。函数式编程避免了改变对象的状态,因为状态没有改变,所以结果总是一样的。这也简化了调试。 简而言之,函数式编程就是用函数来表示程序结构。从这个意义上说,我们应该把函数看作是数学中的函数,而不是编程语言(如 C)中使用的术语。在 Java 中,我们有方法,当我们遵循函数编程范式时,我们创建和使用的方法的行为类似于数学函数。如果一个方法无论调用多少次都给出相同的结果,那么它就是函数性的,就像`sin(0)`总是零一样。函数式编程避免了改变对象的状态,因为状态没有改变,所以结果总是一样的。这也简化了调试。
如果函数曾经为给定的参数返回了某个值,它将始终返回相同的值。我们还可以将代码作为计算的声明来读取,而不是作为一个接一个执行的命令来读取。如果执行顺序不重要,那么代码的可读性也可能增加。 如果函数曾经为给定的参数返回了某个值,它将始终返回相同的值。我们还可以将代码作为计算的声明来读取,而不是作为一个接一个执行的命令来读取。如果执行顺序不重要,那么代码的可读性也可能增加。
...@@ -689,7 +689,7 @@ Java 通过 lambda 表达式和流帮助实现函数式编程风格。请注意 ...@@ -689,7 +689,7 @@ Java 通过 lambda 表达式和流帮助实现函数式编程风格。请注意
# λ # λ
在编写异常抛出测试时,我们已经在第 3 章中使用了 lambda 表达式,*优化了排序代码 Professional*。在该代码中,我们将比较器设置为一个特殊值,该值在每次调用时抛出`RuntimeException` 在编写异常抛出测试时,我们已经在第 3 章中使用了 lambda 表达式,“优化专业排序代码”。在该代码中,我们将比较器设置为一个特殊值,该值在每次调用时抛出`RuntimeException`
```java ```java
sort.setComparator((String a, String b) -> { sort.setComparator((String a, String b) -> {
...@@ -753,9 +753,9 @@ sort.setComparator((var a, var b) -> { ...@@ -753,9 +753,9 @@ sort.setComparator((var a, var b) -> {
首先要澄清的是,流与输入和输出流没有任何关系,除了名称。它们是完全不同的东西。流更像是具有一些显著差异的集合。(如果没有区别,它们就只是集合。)流本质上是可以顺序或并行运行的操作管道。他们从收集或其他来源获得数据,包括动态制造的数据。 首先要澄清的是,流与输入和输出流没有任何关系,除了名称。它们是完全不同的东西。流更像是具有一些显著差异的集合。(如果没有区别,它们就只是集合。)流本质上是可以顺序或并行运行的操作管道。他们从收集或其他来源获得数据,包括动态制造的数据。
流支持对多个数据执行相同的计算。该结构称为**单指令多数据****SIMD**。别害怕这个表情。这是一件非常简单的事情。这本书我们已经做了很多次了。循环也是一种 SIMD 结构。当我们循环检查类以查看其中是否有一个反对该顺序时,我们对每个和每个检查程序执行相同的指令。多个检查器意味着多个数据。 流支持对多个数据执行相同的计算。该结构称为**单指令多数据****SIMD**。别害怕这个表情。这是一件非常简单的事情。这本书我们已经做了很多次了。循环也是一种 SIMD 结构。当我们循环检查类以查看其中是否有一个反对该顺序时,我们对每个和每个检查程序执行相同的指令。多个检查器意味着多个数据。
循环的一个问题是,我们定义了不需要的执行顺序。在跳棋的情况下,我们并不关心跳棋的执行顺序。我们关心的是,所有人都同意这个命令。在编程循环时,我们仍然指定一些顺序。这来自循环的本质,我们无法改变这一点。他们就是这样工作的。然而,如果我们能,不知何故,说*“对每个检查者做这个和那个”*,那就太好了。这就是溪流发挥作用的地方。 循环的一个问题是,我们定义了不需要的执行顺序。在跳棋的情况下,我们并不关心跳棋的执行顺序。我们关心的是,所有人都同意这个命令。在编程循环时,我们仍然指定一些顺序。这来自循环的本质,我们无法改变这一点。他们就是这样工作的。然而,如果我们能,不知何故,说“对每个检查者做这个和那个”,那就太好了。这就是溪流发挥作用的地方。
另一点是,使用循环的代码更重要,而不是描述性的。当我们阅读循环构造的程序时,我们将重点放在各个步骤上。我们首先看到循环中的命令是做什么的。这些命令作用于数据的单个元素,而不是整个集合或数组。 另一点是,使用循环的代码更重要,而不是描述性的。当我们阅读循环构造的程序时,我们将重点放在各个步骤上。我们首先看到循环中的命令是做什么的。这些命令作用于数据的单个元素,而不是整个集合或数组。
...@@ -803,7 +803,7 @@ for (ConsistencyChecker checker :checkers) { ...@@ -803,7 +803,7 @@ for (ConsistencyChecker checker :checkers) {
还有一些接口,例如`BooleanSupplier``DoubleConsumer``DoubleToIntFunction`等等,它们与原语`boolean``double``int`一起工作。不同参数类型和返回值的可能组合的数量是无限的。。。几乎。 还有一些接口,例如`BooleanSupplier``DoubleConsumer``DoubleToIntFunction`等等,它们与原语`boolean``double``int`一起工作。不同参数类型和返回值的可能组合的数量是无限的。。。几乎。
**Fun fact**: To be very precise, it is not infinite. A method can have at most 254 arguments. This limit is specified in the JVM and not in the Java language specification. Of course, one is useless without the other. There are 8 primitive types (plus `Object`, plus the possibility that there are less than 254 arguments), which means that the total number of possible functional interfaces is 10<sup>254</sup>, give or take a few magnitudes. Almost infinite! **有趣的事实**:确切地说,它不是无限的。 一个方法最多可以有 254 个参数。 此限制是在 JVM 中指定的,而不是在 Java 语言规范中指定的。 当然,一个没有另一个就没有用。 有 8 种原始类型(加上“对象”,再加上少于 254 个参数的可能性),这意味着可能的函数时接口总数为`10 ** 254`,给出或取几个幅度。 几乎是无限的!
我们不应该期望在这个包的 JDK 中定义所有可能的接口。这些只是最有用的接口。例如,没有使用`short``char`的接口。如果我们需要这样的东西,那么我们可以在代码中定义`interface`。或者只是仔细想想,找出如何使用一个已经定义好的。(我在职业生涯中从未使用过`short`型号。从来就不需要它。) 我们不应该期望在这个包的 JDK 中定义所有可能的接口。这些只是最有用的接口。例如,没有使用`short``char`的接口。如果我们需要这样的东西,那么我们可以在代码中定义`interface`。或者只是仔细想想,找出如何使用一个已经定义好的。(我在职业生涯中从未使用过`short`型号。从来就不需要它。)
...@@ -895,7 +895,7 @@ return order.getItems().stream() ...@@ -895,7 +895,7 @@ return order.getItems().stream()
这样,我们就可以引用在某个实例上工作的实例方法。在我们的示例中,实例是由`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)`相同。 也可以引用实例方法,而不给出应该调用它的引用。这可以在函数式接口方法有一个额外的第一个参数的地方使用,这个参数将用作实例。我们已经在第 3 章中使用过了,“优化专业排序代码”,当我们通过`String::compareTo`时,当期望的参数是`Comparator`时。`compareTo()`方法需要一个参数,而`Comparator`接口中的`compare()`方法需要两个参数。在这种情况下,第一个参数将用作必须调用`compare()`的实例,第二个参数将传递给`compare()`。在这种情况下,`String::compareTo`与写入 lambda 表达式`(String a, String b) -> a.compareTo(b)`相同。
最后但并非最不重要的一点,我们可以使用构造器的方法引用。当我们需要`Supplier``Object`时,我们可以写`Object::new` 最后但并非最不重要的一点,我们可以使用构造器的方法引用。当我们需要`Supplier``Object`时,我们可以写`Object::new`
...@@ -907,7 +907,7 @@ return order.getItems().stream() ...@@ -907,7 +907,7 @@ return order.getItems().stream()
`filter()`方法使用`Predicate`并创建一个只包含与谓词匹配的元素的流。在本例中,我们使用了对`static`方法的引用。`filter()`方法不会改变流的类型。它只过滤掉元素。 `filter()`方法使用`Predicate`并创建一个只包含与谓词匹配的元素的流。在本例中,我们使用了对`static`方法的引用。`filter()`方法不会改变流的类型。它只过滤掉元素。
我们应用的下一种方法是有点反功能。纯函数流方法不会改变对象的状态。它们创建返回的新对象,但除此之外,没有副作用。`peek()`它本身没有什么不同,因为它只返回一个与应用的元素相同的流。然而,这种*没有操作*功能,诱使新手程序员做一些非功能性的事情,编写带有副作用的代码。毕竟,如果调用它没有(副作用)的话,为什么要使用它? 我们应用的下一种方法是有点反功能。纯函数流方法不会改变对象的状态。它们创建返回的新对象,但除此之外,没有副作用。`peek()`它本身没有什么不同,因为它只返回一个与应用的元素相同的流。然而,这种*操作*功能,诱使新手程序员做一些非功能性的事情,编写带有副作用的代码。毕竟,如果调用它没有(副作用)的话,为什么要使用它?
```java ```java
.peek(pi -> { .peek(pi -> {
...@@ -949,7 +949,7 @@ return order.getItems().stream() ...@@ -949,7 +949,7 @@ return order.getItems().stream()
此方法期望 lambda、方法引用或作为参数传递给它的任何内容为调用该方法的原始流的每个元素创建一个全新的对象流。然而,结果不是流的流,这也是可能的,而是返回的流被连接成一个巨大的流。 此方法期望 lambda、方法引用或作为参数传递给它的任何内容为调用该方法的原始流的每个元素创建一个全新的对象流。然而,结果不是流的流,这也是可能的,而是返回的流被连接成一个巨大的流。
如果我们应用它的流是一个整数流,比如 1,2,3,…,并且每个数的函数`n`返回一个包含三个元素的流`n`*n+1**n+2*,那么得到的流`flatMap()`生成一个包含 1,2,3,2,3,4,4,5、6 等等。 如果我们应用它的流是一个整数流,比如 1,2,3,…,并且每个数的函数`n`返回一个包含三个元素的流`n``n+1``n+2`,那么得到的流`flatMap()`生成一个包含 1,2,3,2,3,4,4,5、6 等等。
最后,我们的流应该被收集到一个`Set`。这是通过调用`collector()`方法完成的: 最后,我们的流应该被收集到一个`Set`。这是通过调用`collector()`方法完成的:
...@@ -985,7 +985,7 @@ public Map<OrderItem, ProductInformation> collectProductInformation(Order order) ...@@ -985,7 +985,7 @@ public Map<OrderItem, ProductInformation> collectProductInformation(Order order)
简单的技巧是将收集到的值存储在`Map`中,如果不是`null`,则只返回已经计算的值,这样在处理同一 HTTP 请求时,如果多次调用此方法,可能会节省大量服务调用。 简单的技巧是将收集到的值存储在`Map`中,如果不是`null`,则只返回已经计算的值,这样在处理同一 HTTP 请求时,如果多次调用此方法,可能会节省大量服务调用。
这种结构有两种编码方式。一种是检查`Map`的非空性,如果`Map`已经存在则返回。这种模式被广泛使用,并有一个名字。如果,则称为保护*。在这种情况下,方法中有多个 return 语句,这可能被视为一个弱点或反模式。另一方面,该方法的制表法是一个标签浅。这是一个品味的问题,如果你发现自己正处于一个或另一个解决方案的争论中,那么就帮自己一个忙,让你的同伴在这个话题上获胜,并为更重要的问题节省精力,例如,你应该使用流还是简单的旧循环。* 这种结构有两种编码方式。一种是检查`Map`的非空性,如果`Map`已经存在则返回。这种模式被广泛使用,并有一个名字,称为保护。在这种情况下,方法中有多个 return 语句,这可能被视为一个弱点或反模式。另一方面,该方法的制表法是一个标签浅。这是一个品味的问题,如果你发现自己正处于一个或另一个解决方案的争论中,那么就帮自己一个忙,让你的同伴在这个话题上获胜,并为更重要的问题节省精力,例如,你应该使用流还是简单的旧循环。
现在,让我们看看如何将此解决方案转换为功能样式: 现在,让我们看看如何将此解决方案转换为功能样式:
...@@ -1081,7 +1081,7 @@ public boolean containsOneOf(String... ids) { ...@@ -1081,7 +1081,7 @@ public boolean containsOneOf(String... ids) {
在我们的示例中,简单的跳棋可能是一个不太可能导致严重错误的区域。这不是不可能的,但代码是如此简单…是的,我知道这样的论点有点可疑,但让我们假设,这些小例程可以用更少的测试和更简单的方式比其他部分的代码来改变。那么,如何将这些小脚本的代码分离开来,使它们不需要技术版本、应用程序的新版本,甚至不需要重新启动应用程序?我们有一个新产品,需要一个新的检查,我们希望有一些方法,注入这个检查到应用程序环境中,没有任何服务中断。 在我们的示例中,简单的跳棋可能是一个不太可能导致严重错误的区域。这不是不可能的,但代码是如此简单…是的,我知道这样的论点有点可疑,但让我们假设,这些小例程可以用更少的测试和更简单的方式比其他部分的代码来改变。那么,如何将这些小脚本的代码分离开来,使它们不需要技术版本、应用程序的新版本,甚至不需要重新启动应用程序?我们有一个新产品,需要一个新的检查,我们希望有一些方法,注入这个检查到应用程序环境中,没有任何服务中断。
我们选择的解决方案是脚本。Java 程序可以执行用*JavaScript**Groovy**Jython*(即*JVM*版本的语言*Python*等多种语言编写的脚本。除了*JavaScript*之外,这些语言的语言解释器都不是 JDK 的一部分,但是它们都提供了一个标准接口,这个接口在 JDK 中定义。结果是,我们可以在代码中实现脚本执行,提供脚本的开发人员可以自由选择任何可用的语言;我们不需要关心执行一个*JavaScript*代码。我们将使用与执行*Groovy**Jython*相同的 API。我们唯一应该知道的是剧本是用什么语言写的。这通常很简单,我们可以从文件扩展名猜测,如果猜测不够,我们可以要求脚本开发人员将*JavaScript*放入扩展名为`.js`的文件中,*Jython*放入扩展名为`.jy``.py`的文件中,*Groovy*放入扩展名为`.groovy`的文件中,等等。同样重要的是要注意,如果我们希望我们的程序执行这些语言之一,我们应该确保解释器在类路径上。在*JavaScript*的情况下,这是给定的,因此,通过本章的演示,我们将用*JavaScript*来编写我们的脚本。不会有太多;毕竟,这是一本 Java 书,而不是一本*JavaScript*书。 我们选择的解决方案是脚本。Java 程序可以执行用 *JavaScript**Groovy**Jython*(即 *JVM* 版本的 *Python* 语言)等多种语言编写的脚本。除了 *JavaScript* 之外,这些语言的语言解释器都不是 JDK 的一部分,但是它们都提供了一个标准接口,这个接口在 JDK 中定义。结果是,我们可以在代码中实现脚本执行,提供脚本的开发人员可以自由选择任何可用的语言;我们不需要关心执行一个 *JavaScript* 代码。我们将使用与执行 *Groovy**Jython*相同的 API。我们唯一应该知道的是剧本是用什么语言写的。这通常很简单,我们可以从文件扩展名猜测,如果猜测不够,我们可以要求脚本开发人员将 *JavaScript* 放入扩展名为`.js`的文件中,*Jython* 放入扩展名为`.jy``.py`的文件中,*Groovy* 放入扩展名为`.groovy`的文件中,等等。同样重要的是要注意,如果我们希望我们的程序执行这些语言之一,我们应该确保解释器在类路径上。在 *JavaScript* 的情况下,这是给定的,因此,通过本章的演示,我们将用 *JavaScript* 来编写我们的脚本。不会有太多;毕竟,这是一本 Java 书,而不是一本 *JavaScript* 书。
当我们想通过编程方式配置或扩展应用程序时,脚本通常是一个很好的选择。这是我们的案子。 当我们想通过编程方式配置或扩展应用程序时,脚本通常是一个很好的选择。这是我们的案子。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册