diff --git a/new/java-proj/8.md b/new/java-proj/8.md index 09526f2a95296f56f968df4438b9696342199926..5437912934421de03454648fd8251d3155255639 100644 --- a/new/java-proj/8.md +++ b/new/java-proj/8.md @@ -60,9 +60,9 @@ public class OrderController { } ``` -我们在这个控制器`order`中只处理一个请求。这被映射到 URL,`/order`。order 从 JSON 自动转换为请求体中的 order 对象。这就是`@RequestBody`注释要求 Spring 为我们做的事情。控制器的功能只是检查顺序的一致性。如果订单一致,那么我们接受订单;否则,我们拒绝订单。实际例子还将检查订单是否不仅一致,而且是否来自有资格购买这些产品的客户,以及根据生产商的承诺和交货期,产品是否在仓库中可用,或者至少可以交货。 +我们在这个控制器`order`中只处理一个请求。这被映射到 URL,`/order`。订单从 JSON 自动转换为请求体中的订单对象。这就是`@RequestBody`注释要求 Spring 为我们做的事情。控制器的功能只是检查顺序的一致性。如果订单一致,那么我们接受订单;否则,我们拒绝订单。实际例子还将检查订单是否不仅一致,而且是否来自有资格购买这些产品的客户,以及根据生产商的承诺和交货期,产品是否在仓库中可用,或者至少可以交货。 -为了检查订单的一致性,我们需要一些能帮我们完成这项工作的东西。因为我们知道我们必须模块化代码,并且不能在一个类中实现太多的东西,所以我们需要一个 checker 对象。这是根据类上的注释以及`@Autowired`对控制器的构造器自动提供的。 +为了检查订单的一致性,我们需要一些能帮我们完成这项工作的东西。因为我们知道我们必须模块化代码,并且不能在一个类中实现太多的东西,所以我们需要一个检查器对象。这是根据类上的注释以及`@Autowired`对控制器的构造器自动提供的。 `Order`类是一个简单的 bean,只列出以下项: @@ -80,7 +80,7 @@ public class Order { } ``` -包的名称为`dtos`,代表**数据传输对象**(**DTO**)的复数形式。DTO 是用于在不同组件(通常通过网络)之间传输数据的对象。由于另一方可以用任何语言实现,封送可以是 JSON、XML 或其他一些只能传递数据的格式。这些类没有真正的方法。dto 通常只有字段、setter 和 getter。 +包的名称为`dtos`,代表**数据传输对象**(**DTO**)的复数形式。DTO 是用于在不同组件(通常通过网络)之间传输数据的对象。由于另一方可以用任何语言实现,封送可以是 JSON、XML 或其他一些只能传递数据的格式。这些类没有真正的方法。DTO 通常只有字段、设置器和获取器。 以下是包含订单中一个项目的类: @@ -134,7 +134,7 @@ public class Confirmation { # 一致性检查程序 -我们有一个一致性检查器类,它的一个实例被注入到控制器中。这个类用于检查一致性,但实际上它本身并不执行检查。它只控制我们提供的不同的 checker,并逐个调用它们来完成真正的工作。 +我们有一个一致性检查器类,它的一个实例被注入到控制器中。这个类用于检查一致性,但实际上它本身并不执行检查。它只控制我们提供的不同的检查器,并逐个调用它们来完成真正的工作。 我们要求一致性检查器(例如在订购台灯时检查订单是否包含电源线的检查器)实现`ConsistencyChecker`接口: @@ -153,11 +153,11 @@ public interface ConsistencyChecker { 我们知道,在开发之初,我们将有很多一致性检查,并不是所有的订单都相关。我们希望避免为每个订单调用每个检查器。为此,我们实现了一些过滤。我们让产品指定他们需要什么类型的检查。这是一段产品信息,如尺寸或描述。为了适应这种情况,我们需要扩展`ProductInformation`类。 -我们将创建每个`ConsistencyChecker`接口,将类实现为一个 springbean(用`@Component`注释进行注释),同时,我们将用一个注释对它们进行注释,该注释指定它们实现的检查类型。同时,`ProductInformation`被扩展,包含一组`Annotation`类对象,这些对象指定要调用哪些 checker。我们可以简单地列出 checker 类,而不是注释,但是这给了我们在配置产品和注释之间的映射时更多的自由。注释指定产品的性质,并对 checker 类进行注释。台灯是`PoweredDevice`类型,checker 类`NeedPowercord`用`@PoweredDevice`注释。如果有任何其他类型的产品也需要电源线,那么该类型的注释应该添加到`NeedPowercord`类中,我们的代码就可以工作了。既然我们开始深入研究注释和注释处理,我们就必须首先了解注释到底是什么。我们从第 3 章“优化专业排序代码”开始就已经使用了注释,但我们所知道的只是如何使用它们,如果不了解我们所做的事情,这通常是危险的。 +我们将创建每个`ConsistencyChecker`接口,将类实现为一个 SpringBean(用`@Component`注释进行注释),同时,我们将用一个注释对它们进行注释,该注释指定它们实现的检查类型。同时,`ProductInformation`被扩展,包含一组`Annotation`类对象,这些对象指定要调用哪些检查器。我们可以简单地列出检查器类,而不是注释,但是这给了我们在配置产品和注释之间的映射时更多的自由。注释指定产品的性质,并对检查器类进行注释。台灯是`PoweredDevice`类型,检查器类`NeedPowercord`用`@PoweredDevice`注释。如果有任何其他类型的产品也需要电源线,那么该类型的注释应该添加到`NeedPowercord`类中,我们的代码就可以工作了。既然我们开始深入研究注释和注释处理,我们就必须首先了解注释到底是什么。我们从第 3 章“优化专业排序代码”开始就已经使用了注释,但我们所知道的只是如何使用它们,如果不了解我们所做的事情,这通常是危险的。 # 注释 -注释前面带有`@`字符,可以附加到包、类、接口、字段、方法、方法参数、泛型类型声明和用法,最后附加到注释。注释几乎可以在任何地方使用,它们被用来描述一些程序元信息。例如,`@RestController`注释不会直接改变`OrderController`类的行为。类的行为由其内部的 Java 代码描述。注释有助于 Spring 理解类是什么以及如何使用它。当 Spring 扫描所有包和类以发现不同的 springbean 时,它会看到类上的注释并将其考虑在内。这个类上可能还有 Spring 不理解的其他注释。它们可能被其他框架或程序代码使用。Spring 将它们视为任何行为良好的框架。例如,正如我们稍后将看到的,在我们的代码库中,我们有一个`NeedPowercord`类,它是一个 springbean,因此用`@Component`注释进行了注释。同时,还附有`@PoweredDevice`注释。Spring 不知道什么是电动设备。这是我们定义和使用的东西。Spring 忽略了这一点。 +注释前面带有`@`字符,可以附加到包、类、接口、字段、方法、方法参数、泛型类型声明和用法,最后附加到注释。注释几乎可以在任何地方使用,它们被用来描述一些程序元信息。例如,`@RestController`注释不会直接改变`OrderController`类的行为。类的行为由其内部的 Java 代码描述。注释有助于 Spring 理解类是什么以及如何使用它。当 Spring 扫描所有包和类以发现不同的 SpringBean 时,它会看到类上的注释并将其考虑在内。这个类上可能还有 Spring 不理解的其他注释。它们可能被其他框架或程序代码使用。Spring 将它们视为任何行为良好的框架。例如,正如我们稍后将看到的,在我们的代码库中,我们有一个`NeedPowercord`类,它是一个 SpringBean,因此用`@Component`注释进行了注释。同时,还附有`@PoweredDevice`注释。Spring 不知道什么是电动设备。这是我们定义和使用的东西。Spring 忽略了这一点。 包、类、接口、字段等可以附加许多注释。这些注释应该简单地写在它们所附加的语法单元声明的前面。 @@ -236,7 +236,7 @@ public @interface ParameteredPoweredDevice { } ``` -由于我们指定的值应该是常量并且在编译时是可计算的,所以复杂类型的使用并不多。注释参数通常是字符串、整数,有时还包括 double 或其他基本类型。语言规范给出的确切类型列表如下: +由于我们指定的值应该是常量并且在编译时是可计算的,所以复杂类型的使用并不多。注释参数通常是字符串、整数,有时还包括`double`或其他基本类型。语言规范给出的确切类型列表如下: * 原语(`double`、`int`等) * 字符串 @@ -347,7 +347,7 @@ public class NeedPowercord implements ConsistencyChecker { private List> check; ``` -因为这是一个 DTO,而且 Spring 需要 setter 和 getter,所以我们还将向它添加一个新的 getter 和 setter。该字段将包含每个类为我们的一个注释实现的类的列表,以及内置的 JDK 接口`Annotation`,因为 Java 编译器是通过这种方式生成它们的。在这一点上,这可能有点模糊,但我保证黎明将破晓,隧道尽头将有光明。 +因为这是一个 DTO,而且 Spring 需要设置器和获取器,所以我们还将向它添加一个新的设置器和获取器。该字段将包含每个类为我们的一个注释实现的类的列表,以及内置的 JDK 接口`Annotation`,因为 Java 编译器是通过这种方式生成它们的。在这一点上,这可能有点模糊,但我保证黎明将破晓,隧道尽头将有光明。 为了获得产品信息,我们必须根据 ID 进行查找。这是我们在上一章中开发的接口和服务,只是这次我们有了另一个新领域。事实上,这是一个显著的差异,尽管`ProductLookup`接口根本没有改变。在最后一章中,我们开发了两个版本。其中一个版本正在从属性文件读取数据,而另一个版本正在连接到 REST 服务。 @@ -412,9 +412,9 @@ public class ResourceBasedProductLookup implements ProductLookup { } ``` -产品的类型是在 JSON 数组中指定的。在本例中,此数组只有一个元素,该元素是表示产品类型的注释接口的完全限定名。当 JSON marshaller 将 JSON 转换为 Java 对象时,它会识别出需要此信息的字段是一个`List`,因此它会将数组转换为一个列表,以及从`String`到`Class`对象中表示注释接口的元素。 +产品的类型是在 JSON 数组中指定的。在本例中,此数组只有一个元素,该元素是表示产品类型的注释接口的完全限定名。当 JSON Marshaller 将 JSON 转换为 Java 对象时,它会识别出需要此信息的字段是一个`List`,因此它会将数组转换为一个列表,以及从`String`到`Class`对象中表示注释接口的元素。 -现在我们已经从 JSON 格式的资源中加载了资源,并且我们已经看到了在使用 Spring 时读取 JSON 数据是多么容易,我们可以回到顺序一致性检查。`Checker`类实现了收集可插入 checker 并调用它们的逻辑。它还实现了基于注释的筛选,以避免调用我们在实际订单中实际产品并不需要的检查: +现在我们已经从 JSON 格式的资源中加载了资源,并且我们已经看到了在使用 Spring 时读取 JSON 数据是多么容易,我们可以回到顺序一致性检查。`Checker`类实现了收集可插入检查器并调用它们的逻辑。它还实现了基于注释的筛选,以避免调用我们在实际订单中实际产品并不需要的检查: ```java package packt.java11.bulkorder.services; @@ -460,17 +460,17 @@ public class Checker { } ``` -其中一件有趣的事情是,弹簧自动布线是非常聪明的。我们有一个`Collection`类型的字段。通常,如果只有一个类与要连接的资源具有相同的类型,则自动连接可以工作。在我们的例子中,因为这是一个集合,所以我们没有任何这样的候选者,但是我们有许多`ConsistencyChecker`类。我们所有的 checker 都实现了这个接口,Spring 识别它,实例化它们,神奇地创建它们的集合,并将集合注入这个字段。 +其中一件有趣的事情是,弹簧自动布线是非常聪明的。我们有一个`Collection`类型的字段。通常,如果只有一个类与要连接的资源具有相同的类型,则自动连接可以工作。在我们的例子中,因为这是一个集合,所以我们没有任何这样的候选者,但是我们有许多`ConsistencyChecker`类。我们所有的检查器都实现了这个接口,Spring 识别它,实例化它们,神奇地创建它们的集合,并将集合注入这个字段。 通常,一个好的框架在逻辑上工作。我不知道春天的这个特征,但我认为这是合乎逻辑的,而且神奇地,它起作用了。如果事情是合乎逻辑的,并且只是工作的话,你不需要阅读和记住文档。不过,稍微小心一点也不会有任何危害。在我意识到这个功能是这样工作的之后,我在文档中查阅了它,以看到这确实是 Spring 的一个保证特性,而不是仅仅发生在工作中的特性,而是在未来版本中可能会发生更改而不需要注意。仅使用保证功能是非常重要的,但在我们的行业中经常被忽略。 -调用`isConsistent()`方法时,首先将产品信息收集到`HashMap`中,为每个`OrderItem`分配一个`ProductInformation`实例。这是在一个单独的班级里完成的。在此之后,`ProductsCheckerCollector`收集一个或多个产品项所需的`ConsistencyChecker`实例。当我们拥有这个集合时,我们只需要调用那些用这个集合中的注释之一进行注释的 checker。我们循环着做。 +调用`isConsistent()`方法时,首先将产品信息收集到`HashMap`中,为每个`OrderItem`分配一个`ProductInformation`实例。这是在一个单独的班级里完成的。在此之后,`ProductsCheckerCollector`收集一个或多个产品项所需的`ConsistencyChecker`实例。当我们拥有这个集合时,我们只需要调用那些用这个集合中的注释之一进行注释的检查器。我们循环着做。 在这段代码中,我们使用反射。我们循环每个检查器都有的注释。为了获取注释集合,我们调用`checker.getClass().getAnnotations()`。此调用返回对象集合。每个对象都是一些 JDK 运行时生成的类的实例,这些类实现了我们在其源文件中声明为注释的接口。但是,没有保证动态创建的类只实现我们的`@interface`,而不是其他接口。因此,要获得实际的注释类,必须调用`annotationType()`方法。 -`ProductCheckerCollector`和`ProductInformationCollector`类非常简单,我们将在稍后学习 streams 时讨论它们。在这一点上,当我们使用循环实现它们时,它们将成为一个很好的例子,紧接着,使用流。 +`ProductCheckerCollector`和`ProductInformationCollector`类非常简单,我们将在稍后学习流时讨论它们。在这一点上,当我们使用循环实现它们时,它们将成为一个很好的例子,紧接着,使用流。 -拥有它们,我们最终可以创建实际的 checker 类。帮助我们看到我们的灯有一根电源线的命令如下: +拥有它们,我们最终可以创建实际的检查器类。帮助我们看到我们的灯有一根电源线的命令如下: ```java package packt.java11.bulkorder.checkers; @@ -498,7 +498,7 @@ public class NeedPowercord implements ConsistencyChecker { } ``` -helper 类包含许多 checker 需要的简单方法,例如: +助手类包含许多检查器需要的简单方法,例如: ```java public boolean containsOneOf(String... ids) { @@ -527,7 +527,7 @@ Spring 还使用反射来发现类、方法和字段,并注入对象。它使 在这种情况下我们能做什么?从商业角度来看,要求所有供应商重写他们的跳棋是不可能的,因为他们知道我们有麻烦了,这会给任务贴上一个很高的价格标签。我们的管理者希望避免这一成本,而我们的开发人员也希望表明,我们能够纠正这种情况,创造奇迹(我稍后将对此发表评论)。 -我们可以有一个类,它知道每个 checker 以及如何以多种不同的方式调用它们。这将要求我们在系统中引入新检查器时维护所述类,我们希望避免这种情况。我们使用的整个插件架构最初就是为了这个目的而发明的。 +我们可以有一个类,它知道每个检查器以及如何以多种不同的方式调用它们。这将要求我们在系统中引入新检查器时维护所述类,我们希望避免这种情况。我们使用的整个插件架构最初就是为了这个目的而发明的。 如果我们知道一个对象只有一个声明的方法,而这个方法接受一个命令作为参数,那么我们如何调用这个对象上的方法呢?这就是反射进入画面的地方。我们没有调用`checker.isInconsistent(order)`,而是实现了一个小的`private`方法`isInconsistent()`,通过反射调用这个方法,不管它叫什么名字: @@ -555,7 +555,7 @@ private boolean isInconsistent(ConsistencyChecker checker, Order order) { } ``` -通过调用`getClass()`方法可以得到对象的类,在表示类本身的对象上,可以调用`getDeclaredMethods`。幸运的是,checker 类没有被很多方法乱放,因此我们检查 checker 类中声明的方法是否只有一个。注意,反射库中也有一个`getMethods()`方法,但它将始终返回多个方法。它返回声明的和继承的方法。因为每个类都继承了`java.lang.Object`,所以至少会有`Object`类的方法。 +通过调用`getClass()`方法可以得到对象的类,在表示类本身的对象上,可以调用`getDeclaredMethods`。幸运的是,检查器类没有被很多方法乱放,因此我们检查检查器类中声明的方法是否只有一个。注意,反射库中也有一个`getMethods()`方法,但它将始终返回多个方法。它返回声明的和继承的方法。因为每个类都继承了`java.lang.Object`,所以至少会有`Object`类的方法。 之后,我们尝试使用表示反射类中方法的`Method`对象来调用该类。请注意,这个`Method`对象并没有直接连接到实例。我们从类中检索该方法,因此,当我们调用它时,应该将它应该处理的对象作为第一个参数传递。这样,`x.y(z)`就变成了`method.invoke(x,z)`。`invoke()`的最后一个参数是作为`Object`数组传递的变量数。在大多数情况下,当我们调用一个方法时,我们知道代码中的参数,即使我们不知道方法的名称并且必须使用反射。当连参数都不知道,但作为计算的问题是可用的时,我们必须将它们作为一个`Object`数组传递。 @@ -613,7 +613,7 @@ private Method getSingleDeclaredPublicMethod( 有一个例外,那就是`volatile`。该位被重新用于信号桥方法。桥接方法是由编译器自动创建的,并且可能有一些我们在本书中没有讨论的深层次和复杂的问题。重复使用同一位不会造成混淆,因为字段可以是`volatile`,但作为字段,它不能是桥接方法。显然,字段是字段而不是方法。同样地,方法不能是`volatile`字段。一般规则如下:不要在反射对象没有意义的地方使用方法;否则,要知道你在做什么。 -一个新版本的 checker 意外地将 check 方法实现为一个`private`包,这使得故事情节更加复杂,程序员只是忘记了使用`public`关键字。为了简单起见,让我们假设类再次只声明一个方法,但它不是公共的。我们如何使用反射来解决这个问题? +一个新版本的检查器意外地将`check`方法实现为一个`private`包,这使得故事情节更加复杂,程序员只是忘记了使用`public`关键字。为了简单起见,让我们假设类再次只声明一个方法,但它不是公共的。我们如何使用反射来解决这个问题? 显然,最简单的解决方案是要求供应商解决问题-这是他们的错。然而,在某些情况下,我们必须为某些问题创建一个解决方案。另一种解决方案是在同一个包中创建一个具有`public`方法的类,从另一个类调用`private`包方法,从而中继另一个类。事实上,这个解决方案,作为这样一个 bug 的解决方案,似乎更符合逻辑,更清晰,但是这次,我们希望使用反射。 @@ -773,7 +773,7 @@ IntStream.iterate( 0, (s) -> s+1 ) 一旦我们有了一个流,我们就可以使用`Stream`接口定义的方法。首先是`forEach()`。此方法有一个参数,通常作为 Lambda 表达式提供,并将为流的每个元素执行 Lambda 表达式。 -在`Checker`类中,我们有`isConsistent()`方法。在这个方法中,有一个循环遍历 checker 类的注释。如果要记录循环中注释实现的接口,可以添加以下内容: +在`Checker`类中,我们有`isConsistent()`方法。在这个方法中,有一个循环遍历检查器类的注释。如果要记录循环中注释实现的接口,可以添加以下内容: ```java for (ConsistencyChecker checker :checkers) { @@ -919,7 +919,7 @@ return order.getItems().stream() 虽然`peek()`方法本身没有任何副作用,但是 Lambda 表达式的执行可能会有副作用。但是,对于其他任何方法也是如此。事实上,在这种情况下,做一些不适当的事情更具诱惑力。不要。我们是有纪律的成年人。正如该方法的名称所示,我们可以窥视流,但我们不应该做任何其他事情。由于编程是一项特殊的活动,在这种情况下,窥视就足够了。这就是我们在代码中实际做的:我们记录一些东西。 -在此之后,我们去掉了没有`ProductInformation`的元素;我们也想去掉有`ProductInformation`的元素,但是没有定义 checker: +在此之后,我们去掉了没有`ProductInformation`的元素;我们也想去掉有`ProductInformation`的元素,但是没有定义检查器: ```java .filter(pi -> pi.getCheck() != null) @@ -959,7 +959,7 @@ return order.getItems().stream() `collector()`方法的参数是(同样,一个过度使用的表达式)`Collector`。它可以用于将流的元素收集到集合中。注意,`Collector`不是函数式接口。你不能仅仅用 Lambda 或者简单的方法来收集一些东西。为了收集元素,我们肯定需要一个地方来收集元素,因为不断更新的元素来自流。`Collector`接口不简单。幸运的是,`java.util.streams.Collectors`类(同样注意复数形式)有许多`static`方法创建并返回`Object`字段,这些字段反过来又创建并返回`Collector`对象。 -其中之一是`toSet()`,它返回一个`Collector`,帮助将流中的元素收集到一个`Set`中。当所有元素都存在时,`collect()`方法将返回`Set`。还有其他一些方法可以帮助收集流元素,方法是将元素相加,计算平均值,或将其转换为 a`List`、`Collection`或 a`Map`。将元素收集到`Map`是一件特殊的事情,因为`Map`的每个元素实际上是一个键值对。当我们看`ProductInformationCollector`时,我们将看到这个例子。 +其中之一是`toSet()`,它返回一个`Collector`,帮助将流中的元素收集到一个`Set`中。当所有元素都存在时,`collect()`方法将返回`Set`。还有其他一些方法可以帮助收集流元素,方法是将元素相加,计算平均值,或将其转换为`List`、`Collection`或`Map`。将元素收集到`Map`是一件特殊的事情,因为`Map`的每个元素实际上是一个键值对。当我们看`ProductInformationCollector`时,我们将看到这个例子。 `ProductInformationCollector`类代码包含`collectProductInformation()`方法,我们将从`Checker`类和`ProductsCheckerCollector`类中使用该方法: @@ -985,7 +985,7 @@ public Map collectProductInformation(Order order) 简单的技巧是将收集到的值存储在`Map`中,如果不是`null`,则只返回已经计算的值,这样在处理同一 HTTP 请求时,如果多次调用此方法,可能会节省大量服务调用。 -这种结构有两种编码方式。一种是检查`Map`的非空性,如果`Map`已经存在则返回。这种模式被广泛使用,并有一个名字,称为保护。在这种情况下,方法中有多个 return 语句,这可能被视为一个弱点或反模式。另一方面,该方法的制表法是一个标签浅。这是一个品味的问题,如果你发现自己正处于一个或另一个解决方案的争论中,那么就帮自己一个忙,让你的同伴在这个话题上获胜,并为更重要的问题节省精力,例如,你应该使用流还是简单的旧循环。 +这种结构有两种编码方式。一种是检查`Map`的非空性,如果`Map`已经存在则返回。这种模式被广泛使用,并有一个名字,称为保护。在这种情况下,方法中有多个`return`语句,这可能被视为一个弱点或反模式。另一方面,该方法的制表法是一个标签浅。这是一个品味的问题,如果你发现自己正处于一个或另一个解决方案的争论中,那么就帮自己一个忙,让你的同伴在这个话题上获胜,并为更重要的问题节省精力,例如,你应该使用流还是简单的旧循环。 现在,让我们看看如何将此解决方案转换为功能样式: @@ -1013,7 +1013,7 @@ public Map collectProductInformation(Order order) 我们使用一个助手类`Tuple`,它只不过是两个`Object`实例,分别命名为`r`和`s`。稍后我们将列出这个类的代码。这很简单。 -在 streams 表达式中,我们首先从集合中创建流,然后将`OrderItem`元素映射到一个由`OrderItem`和`productId`元组组成的流。然后,我们将这些元组映射到现在包含`OrderItem`和`ProductInformation`的元组。这两个映射可以在一个映射调用中完成,该调用将在一个映射调用中执行这两个步骤。我决定在每一行中创建两个简单的步骤,希望得到的代码更容易理解。 +在流表达式中,我们首先从集合中创建流,然后将`OrderItem`元素映射到一个由`OrderItem`和`productId`元组组成的流。然后,我们将这些元组映射到现在包含`OrderItem`和`ProductInformation`的元组。这两个映射可以在一个映射调用中完成,该调用将在一个映射调用中执行这两个步骤。我决定在每一行中创建两个简单的步骤,希望得到的代码更容易理解。 过滤步骤也不是什么新鲜事。它只是过滤掉无效的产品信息元素。实际上应该没有。如果订单包含不存在产品的订单 ID,则会发生这种情况。在下一个语句中,当我们查看收集的产品信息元素的数量,以确定所有项目都具有适当的信息时,就会检查这一点。 @@ -1058,7 +1058,7 @@ public boolean isConsistent(Order order) { 因为您已经学习了大多数重要的流方法,所以这里几乎没有什么新问题。我们可以提到`anyMatch()`方法,如果至少有一个元素,则返回`true`,这样传递给`anyMatch()`的`Predicate`参数就是`true`。它可能还需要一些住宿,这样我们就可以使用另一条溪流中的一条溪流。这很可能是一个例子,当一个流表达式过于复杂,需要使用局部变量分解成更小的片段。 -最后,在离开函数样式之前,我们重写了`CheckHelper`类中的`containsOneOf()`方法。这不包含新元素,将帮助您检查您对`map()`、`filter()`、`flatMap()`和`Collector`的了解。请注意,如我们所讨论的,如果`order`至少包含一个以字符串形式给出的 order id,则此方法返回`true`: +最后,在离开函数样式之前,我们重写了`CheckHelper`类中的`containsOneOf()`方法。这不包含新元素,将帮助您检查您对`map()`、`filter()`、`flatMap()`和`Collector`的了解。请注意,如我们所讨论的,如果`order`至少包含一个以字符串形式给出的订单 ID,则此方法返回`true`: ```java public boolean containsOneOf(String... ids) { @@ -1101,9 +1101,9 @@ public void setCheckScript(String checkScript) { 我们不希望为每个产品指定多个脚本;因此,我们不需要脚本名称列表。我们只有一个由名称指定的脚本。 -老实说,检查器类和注释的数据结构,允许每个产品以及每个 checker 类都有多个注释,这太复杂了。然而,我们无法避免拥有一个足够复杂的结构,可以证明流表达式的能力和能力。既然我们已经讨论了这个主题,我们可以继续使用更简单的数据结构,重点关注脚本执行。 +老实说,检查器类和注释的数据结构,允许每个产品以及每个检查器类都有多个注释,这太复杂了。然而,我们无法避免拥有一个足够复杂的结构,可以证明流表达式的能力和能力。既然我们已经讨论了这个主题,我们可以继续使用更简单的数据结构,重点关注脚本执行。 -我们还必须修改`Checker`类,以便不仅使用 checker 类,而且使用脚本。我们不能扔掉 checker 类,因为当我们意识到我们需要更好的脚本时,我们已经有很多 checker 类,我们没有资金将它们重写为脚本。嗯,是的,我们是在书中,而不是在现实生活中,但在一个企业,这将是事实。这就是为什么在为企业设计解决方案时你应该非常小心的原因。结构和解决方案将存在很长一段时间,仅仅因为一段代码在技术上不是最好的,就很难抛出它。如果它能够工作并且已经存在,那么企业将非常不愿意在代码维护和重构上花钱。 +我们还必须修改`Checker`类,以便不仅使用检查器类,而且使用脚本。我们不能扔掉检查器类,因为当我们意识到我们需要更好的脚本时,我们已经有很多检查器类,我们没有资金将它们重写为脚本。嗯,是的,我们是在书中,而不是在现实生活中,但在一个企业,这将是事实。这就是为什么在为企业设计解决方案时你应该非常小心的原因。结构和解决方案将存在很长一段时间,仅仅因为一段代码在技术上不是最好的,就很难抛出它。如果它能够工作并且已经存在,那么企业将非常不愿意在代码维护和重构上花钱。 总之,我们修改了`Checker`类。我们需要一个新类来执行我们的脚本;因此,我们必须插入一个新的`final`字段,如下所示: @@ -1141,7 +1141,7 @@ public boolean isConsistent(Order order) { 注意,在这段代码中,我们使用并行流,因为,为什么不呢?只要有可能,我们就可以使用并行流(即使是无序的)来告诉底层系统,以及维护代码的程序员,顺序并不重要。 -我们还修改了一个产品 JSON 文件,通过一些注释引用脚本而不是 checker 类: +我们还修改了一个产品 JSON 文件,通过一些注释引用脚本而不是检查器类: ```java { @@ -1195,7 +1195,7 @@ function isInconsistent(order){ myBindings.put("globalVariable",myObject) ``` -我们可以创建`Bindings`并将其传递给`ScriptEngineManager`,但也可以使用它自动创建的方法,并可以直接调用 engine 对象上的`put()`方法。 +我们可以创建`Bindings`并将其传递给`ScriptEngineManager`,但也可以使用它自动创建的方法,并可以直接调用引擎对象上的`put()`方法。 当我们执行脚本时,有两个`Bindings`。一个设置在`ScriptEngineManager`层。这称为全局绑定。还有一个是由`ScriptEngine`自己管理的。这是当地的`Bindings`。从剧本的角度看,没有区别。从嵌入的角度看,存在一定程度的差异。如果我们使用相同的`ScriptEngineManager`来创建多个`ScriptEngine`实例,那么全局绑定将由它们共享。如果一个人得到一个值,所有人都会看到相同的值;如果一个人设置了一个值,其他人都会看到更改后的值。本地绑定特定于它所管理的引擎。由于本书只介绍了 Java 脚本 API,所以我们不做详细介绍,也不使用`Bindings`。我们擅长调用 JavaScript 函数并从中获得结果。 @@ -1231,7 +1231,7 @@ public class CheckerScriptExecutor { 实际上,这是企业应该决定的。如果存在无法执行的检查脚本,则显然是错误的情况。在这种情况下,接受订单并随后手动处理问题会产生一定的成本。由于某些内部错误而拒绝订单或确认对订单流程来说也不是一条愉快的道路。我们必须检查哪种方法对公司造成的损害最小。这当然不是程序员的职责。我们的处境很容易。 -我们假设业务代表说在这种情况下订单应该被拒绝。在现实生活中,类似的决策被业务代表拒绝,他们说这不应该发生,it 部门必须确保程序和整个操作完全没有 bug。这种反应是有心理原因的,但这确实使我们离 Java 编程非常遥远。 +我们假设业务代表说在这种情况下订单应该被拒绝。在现实生活中,类似的决策被业务代表拒绝,他们说这不应该发生,IT 部门必须确保程序和整个操作完全没有 bug。这种反应是有心理原因的,但这确实使我们离 Java 编程非常遥远。 引擎可以执行通过`Reader`或作为`String`传递的脚本。因为现在我们在资源文件中有了脚本代码,所以让引擎读取资源似乎是一个更好的主意,而不是将其读取到一个`String`: