Java 16 新特性介绍

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"本文要点"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16和即将发布的Java 17引入了大量特性和语言增强,有助于提高开发人员的生产力和应用程序性能"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16 Stream API为常用的终端操作提供了很多新方法,有助于减少样板代码的混乱现象"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Record是Java 16中的一项语言新特性,可简洁地定义纯数据类。编译器提供了构造器、访问器和一些常见Object方法的实现"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"模式匹配是Java 16中的另一个新特性,它简化了使用instanceof代码块完成的显式和冗长的转换,此外还有很多好处"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16于2021年3月发布,版本类型是可用于生产的GA构建,我在这段"},{"type":"link","attrs":{"href":"http:\/\/www.infoq.com\/presentations\/new-java-16\/","title":null,"type":null},"content":[{"type":"text","text":"深度视频演示"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中介绍了该版本的新特性。下一个LTS版本Java 17计划于今年9月发布。Java 17将包含许多改进和语言增强,其中大部分是自Java 11以来交付的所有新特性和更改的成果结晶。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"就Java 16中的新特性而言,我将分享Stream API中一项讨喜的更新,然后主要关注语言更改部分。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"从Stream到List"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"List features =\n Stream.of(\"Records\", \"Pattern Matching\", \"Sealed Classes\")\n .map(String::toLowerCase)\n .filter(s -> s.contains(\" \"))\n .collect(Collectors.toList());"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你习惯使用Java Stream API,那么应该会很熟悉上面这个代码段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"这段代码里有一个包含一些字符串的流。我们在它上面映射一个函数,然后过滤这个流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"最后,我们将流物化为一个列表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如你所见,我们通常会调用终端操作collect并给它传递一个收集器。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"这里是很常见的实践——使用collect并将Collectors.toList()传递给它,感觉就像是样板代码一样。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"好消息是,在Java 16中Stream API中添加了一个新方法,使我们能够立即将toList()作为一个流的一个终端操作来调用。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"List features =\n Stream.of(\"Records\", \"Pattern Matching\", \"Sealed Classes\")\n .map(String::toLowerCase)\n .filter(s -> s.contains(\" \"))\n .toList();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在上面的代码中使用这个新方法会生成一个来自这个流,且包含一个空格的字符串的列表。请注意,我们返回的这个列表是一个不可修改的列表。这意味着你不能再从这个终端操作返回的列表中添加或删除任何元素。如果要将流收集到一个可变列表中,则必须继续使用一个带有collect()函数的收集器。所以这个Java 16中新引入的toList()方法真的很讨喜。这个更新应该能让流管道代码块读起来更容易一些。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Stream API的另一个更新是mapMulti()方法。它的用途有点像flatMap()方法。如果你平常用的是flatMap(),并且映射到lambda中的内部流并传递给它,那么mapMulti()为你提供了一种替代方法,你可以将元素推送给一个消费者。我不会在本文中具体介绍这个方法,因为我想讨论的是Java 16中的语言新特性。如果你有兴趣进一步了解mapMulti(),我强烈建议你查看"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/docs\/api\/java.base\/java\/util\/stream\/Stream.html#mapMulti(java.util.function.BiConsumer)","title":null,"type":null},"content":[{"type":"text","text":"Java文档"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"中关于这种方法的介绍。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Records"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中引入的第一个重大语言特性称为记录(records)。记录用来将数据表示为Java代码中的数据,而不是任意类。在Java 16之前,有时我们只是需要表示一些数据,最终却得到了一个任意类,如下面的代码段所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class Product {\n private String name;\n private String vendor;\n Private int price;\n private boolean inStock;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"这里我们有一个Product类,它有四个成员。定义这个类所需的所有信息应该就是这些了。当然,我们需要更多的代码来完成这项工作。例如,我们需要有一个构造器。我们需要有相应的getter方法来获取成员的值。为了补充完整,我们还需要有与我们定义的成员一致的equals()、hashCode()和toString()实现。一些样板代码可以由IDE生成,但这样做会有一些缺陷。你也可以使用Lombok等框架,但它们也有一些缺陷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我们真正需要的是由Java语言提供一种机制,可以更准确地描述拥有纯数据类这个概念。所以在Java 16中我们有了记录的概念。在以下代码段中,我们将Product类重新定义为一个记录。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public record Product(\n String name,\n String vendor,\n int price,\n boolean inStock) {\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"请注意这里引入了新关键字record。我们需要在关键字record之后指定记录类型的名称。在我们的示例中,这个名称是Product。然后我们只需要提供组成这些记录的组件。在这里,我们给出了四个组件的类型和名称以提供它们。然后我们就完成了。Java中的记录是类的一个特殊形式,其中只包含数据。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"记录能给我们带来什么呢?一旦我们有了一个记录声明,我们就会得到一个类,它有一个隐式构造器,接受这个记录的组件的所有值。我们会根据所有记录组件自动获取equals()、hashCode()和toString()方法的实现。此外,我们还为记录中的每个组件获取访问器方法。在上面的例子中,我们得到了一个name方法、一个vendor方法、一个price方法和一个inStock方法,它们分别返回这个记录的组件的实际值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"记录永远是不可变的。这里没有setter方法。一旦使用某些值实例化一个记录,那么你就无法再更改它了。此外,记录类就是最终形式。你可以使用一个记录实现一个接口,但在定义记录时不能扩展其他任何类。总而言之,这里有一些限制。但是记录为我们提供了一种非常强大的方式来在我们的应用程序中简洁地定义纯数据类。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"怎样看待记录"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你应该如何看待和处理这些新的语言元素呢?记录是一种新的、受限形式的类,用于将数据建模为数据。我们不可能向记录添加任何附加状态;除了记录的组件之外,你不能定义(非静态)字段。记录实际上是建模不可变数据的。你也可以将记录视为元组,但它并不只是其他一些语言所有的那种一般意义上的元组,在那种元组里有一些可以由索引引用的任意组件。在Java中,元组元素有实际名称,并且元组类型本身——即记录,也有一个名称,因为名称在Java中很重要。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"记录不适合哪些场景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"有些场景中我们可能会觉得记录用起来并不是很合适。首先,它们并不是任何现有代码的一个样板缩减机制。虽然我们现在有一种非常简洁的方式来定义这些记录,但这并不意味着你的应用程序中的任何数据(如类)都可以轻松地被记录替换,这主要是因为记录存在的一些限制所致。这也不是它真正的设计目标。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"记录的设计目标是提供一种将数据建模为数据的好方法。它也不是JavaBeans的直接替代品,因为正如我之前提到的,访问器这样的方法不符合JavaBeans的get标准。另外JavaBeans通常是可变的,而记录是不可变的。尽管它们的用途有点像,但记录并不会以某种方式取代JavaBean。你也不应该将记录视为值类型。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"值类型可能会在未来的Java版本中作为语言增强引入,其主要关注内存布局和类中数据的有效表示。当然,这两条世界线在未来某一时刻可能会合并在一起,但就目前而言,记录只是表达纯数据类的一种更简洁的方式。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"进一步了解记录"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"考虑以下代码,我们创建了Product类型的记录p1和p2,具有完全相同的值。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Product p1 = new Product(\"peanut butter\", \"my-vendor\", 20, true);\nProduct p2 = new Product(\"peanut butter\", \"my-vendor\", 20, true);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我们可以通过引用相等来比较这些记录,也可以使用equals()方法比较它们,该方法已由记录实现自动提供。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"System.out.println(p1 == p2); \/\/ Prints false\nSystem.out.println(p1.equals(p2)); \/\/ Prints true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"可以看到,这两条记录是两个不同的实例,因此引用对比将给出false。但是当我们使用equals()时,它只查看这两个记录的值,所以它会评估为true。因为它只考虑记录内部的数据。重申一下,相等性和哈希码的实现完全基于我们为记录的构造器提供的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"需要注意的一件事是,你仍然可以覆盖记录定义中的任何访问器方法,或者相等性和哈希码实现。但是,你有责任在记录的上下文中保留这些方法的语义。并且你可以向记录定义添加其他方法。你还可以访问这些新方法中的记录值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一个你可能想在记录中执行的重要特性是验证。例如,你只想在提供给记录构造器的输入有效时才创建记录。传统的验证方法是定义一个带有输入参数的构造器,这些参数在将参数分配给成员变量之前进行验证。但是对于记录而言,我们可以使用一种新格式,即所谓的紧凑构造器。在这种格式中,我们可以省略正式的构造器参数。构造器将隐式地访问组件值。在我们的Product示例中,我们可以说如果price小于零,则抛出一个新的IllegalArgumentException。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public record Product(\n String name,\n String vendor,\n int price,\n boolean inStock) {\n public Product {\n if (price < 0) {\n throw new IllegalArgumentException();\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"从上面的代码段中可以看出,如果价格高于零,我们就不必手动做任何赋值。在编译此记录时,编译器会自动添加从(隐式)构造器参数到记录字段的赋值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果我们愿意,甚至可以进行正则化。例如,我们可以将隐式可用的价格参数设置为一个默认值,而不是在价格小于零时抛出异常。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public Product {\n if (price < 0) {\n price = 100;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"同样,对记录的实际成员的赋值——即作为这个记录定义一部分的最终字段,是由编译器在这个紧凑构造器的末尾自动插入的。总而言之,这是在Java中定义纯数据类的一种非常通用且非常棒的方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你还可以在方法中本地声明和定义记录。如果你想在方法中使用一些中间状态,这会非常方便。例如,假设我们要定义一个打折产品。我们可以定义一个记录,包含Product和一个指示产品是否打折的boolean值。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public static void main(String... args) {\n Product p1 = new Product(\"peanut butter\", \"my-vendor\", 100, true);\n record DiscountedProduct(Product product, boolean discounted) {}\n System.out.println(new DiscountedProduct(p1, true));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"从上面的代码段中可以看出,我们不必为新记录定义提供正文。我们可以使用p1和true作为参数来实例化DiscountedProduct。运行代码时,你会看到它的行为方式与源文件中的顶级记录完全相同。如果你希望在流管道的中间阶段分组某些数据,作为本地构造的记录会非常有用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"你会在哪里使用记录"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"记录有一些显而易见的使用场景。比如说当我们想要使用数据传输对象(Data Transfer Objects,DTO)时就可以使用记录。根据定义,DTO是不需要任何身份或行为的对象。它们只是用来传输数据的。例如,从2.12版本开始,Jackson库支持将记录序列化和反序列化为JSON和其他支持的格式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果你希望一个映射中的键由充当复合键的多个值组成,记录也会很好用,因为你会自动获得equals和hashcode实现的正确行为。由于记录也可以被认为是名义元组(其中每个组件都有一个名称),使用记录将多个值从方法返回给调用者也是很方便的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一方面,我认为记录在Java Persistence API中用的不会很多。如果你想使用记录来表示实体,那实际上是不可能的,因为实体在很大程度上是基于JavaBeans约定。并且实体通常倾向于是可变的。当然,当你在查询中实例化只读视图对象时,有些情况下你可以使用记录代替常规类。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"总而言之,我认为Java中引入记录是一项激动人心的改进。我认为它们会得到广泛使用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"instanceof的模式匹配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中的第二大语言更改是instanceof的模式匹配。这是将模式匹配引入Java的漫长旅程的第一步。就目前而言,我认为Java 16中提供的初期支持已经很不错了。看看下面的代码段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String) {\n String s = (String) o;\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"你可能会认出这种模式,其中一些代码负责检查对象是否是一个类型的实例,在本例中是String类。如果检查通过,我们需要声明一个新的作用域变量,转换并赋值,然后我们才能开始使用这个类型化的变量。在这个示例中,我们需要声明变量s,cast o为一个String,然后调用length()方法。虽然这种办法也能用,但太啰嗦了,而且并没有反映出代码的真实意图。我们有更好的办法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"从Java 16开始,我们可以使用新的模式匹配特性了。使用模式匹配时,我们可以将o匹配一个类型模式,而不是说o是一个特定类型的实例。类型模式由一个类型和一个绑定变量组成。我们来看一个例子。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String s) {\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在上面的代码段中,如果o确实是String的实例,那么String s将立即绑定到o的值。这意味着我们可以立即开始使用s作为一个字符串,而无需在if主体内进行显式转换。这里的另一个好处是s的作用域仅限于if的主体。这里需要注意的一点是,源代码中o的类型不应该是String的子类型,因为如果是这种情况,条件将始终为真。因此一般而言,如果编译器检测到正在测试的对象的类型是模式类型的子类型,则会抛出编译时错误。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一个需要指出的有趣的事情是,编译器很聪明,可以根据条件的计算结果为true还是false来推断s的作用域,正如以下代码段中所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (!(o instanceOf String s)) {\n return 0;\n} else {\n return s.length();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"编译器看到,如果模式匹配不成功,那么在else分支中,我们的s将在String类型的作用域内。并且在if分支s不在作用域内时,我们在作用域内就只有o。这种机制称为流作用域,其中类型模式变量仅在模式实际匹配时才在作用域内。这真的很方便,能够有效简化这段代码。你需要注意这个变化,可能需要一点时间来适应。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一个例子里你也可以清楚地看到这个流的作用。当你重写equals()方法的以下代码实现时,常规的实现是首先检查o是否是MyClass的一个实例。如果是,我们将o转换为MyClass,然后将o的name字段与MyClass的当前实例进行匹配。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic boolean equals(Object o) {\n return (o instanceOf MyClass) &&\n ((MyClass) o).name.equals(name);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我们可以使用新的模式匹配机制来简化这个实现,如下面的代码段所示。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\npublic boolean equals(Object o) {\n return (o instanceOf MyClass m) &&\n m.name.equals(name);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"这里又一次对代码中显式、冗长的转换做了很好的简化。只要用在合适的用例里,模式匹配会抽象出许多样板代码。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"模式匹配:未来发展"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java团队已经勾勒出了模式匹配的一些未来发展方向。当然,团队并没有承诺这些设想何时或如何引入官方语言。在下面的代码段中可以看到,在新的switch表达式中,我们可以像之前讨论的那样使用instanceOf来做类型模式。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"static String format(Object o) {\n return switch(o) {\n case Integer i -> String.format(\"int %d\", i);\n case Double d -> String.format(\"int %f\", d);\n default -> o.toString();\n };\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"在o是整数的情况下,流作用域开始起作用,我们可以立即将变量i用作一个整数。其他情况和默认分支也是如此。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"另一个令人兴奋的新方向是记录模式,我们可以模式匹配我们的记录并立即将组件值绑定到新变量。看看下面的代码段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf Point(int x, int y)) {\n System.out.println(x + y);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我们有一个包含x和y的Point记录。如果对象o确实是一个点,我们将立即将x和y分量绑定到x和y变量并立即开始使用它们。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"数组模式是可能在Java的未来版本中引入的另一种模式匹配。看看下面的代码段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if (o instanceOf String[] {String s1, String s2, ...}) {\n System.out.println(s1 + s2);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"如果o是字符串数组,则可以立即将这个字符串数组的第一部分和第二部分提取到s1和s2。当然,这只适用于字符串数组中有两个或更多元素的情况。我们可以使用三点表示法忽略数组元素的其余部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"总而言之,使用instanceOf进行模式匹配是一个不错的小特性,但它是迈向新未来的一步。我们可能会引入其他类型的模式来帮助编写干净、简单和可读的代码。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"特性预览:密封类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"下面来谈谈密封类(sealed class)这个特性。请注意,这是Java 16中的预览特性,将在Java 17中成为最终版本。你需要将--enable-preview标志传递给编译器调用和JVM调用才能在Java 16中使用这个特性。该特性允许你控制继承层次结构。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"假设你想对一个超类型Option建模,其中你只想有Some和Empty两个子类型。并且你想预防Option类型获得任何扩展。例如,你不想在层次结构中允许Maybe类型。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/89\/89f67aaf464f67ec066a488747d36570.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"因此,你已经详细描述了Option类型的所有子类型。如你所知,目前在Java中控制继承的唯一工具是通过final关键字。这意味着根本不能有任何子类,但这不是我们想要的。有一些解决方法可以在没有密封类的情况下建模这个特性,但有了密封类后就容易多了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"密封类特性带有新的关键字sealed和permits。看看下面的代码段。"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public sealed class Option\n permits Some, Empty {\n ...\n}\npublic final class Some\n extends Option {\n ...\n}\npublic final class Empty\n extends Option {\n ...\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我们可以定义要sealed的Option类。然后,在类声明之后,我们使用permit关键字来规定只允许Some和Empty类扩展Option类。然后,我们可以像往常一样将Some和Empty定义为类。我们希望将这些子类设为final,以防止进一步继承。现在系统就不能编译其他类来扩展Option类了,这是由编译器通过密封类机制强制执行的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"关于此特性还有很多要说的内容,本文不能一一尽述。如果你有兴趣了解更多信息,我建议你浏览"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/360","title":null,"type":null},"content":[{"type":"text","text":"密封类Java增强提案页面"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"JEP360。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"Java 16中还有很多我们无法在本文中介绍的内容,例如"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/338","title":null,"type":null},"content":[{"type":"text","text":"Vector API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"、"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/389","title":null,"type":null},"content":[{"type":"text","text":"Foreign Linker API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"和"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/393","title":null,"type":null},"content":[{"type":"text","text":"Foreign-Memory Access API"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"等孵化器API都非常有前途。并且新版在JVM层面也做了很多改进。例如"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/gctuning\/z-garbage-collector.html","title":null,"type":null},"content":[{"type":"text","text":"ZGC"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"有一些性能改进;在JVM中做了一些"},{"type":"link","attrs":{"href":"https:\/\/openjdk.java.net\/jeps\/387","title":null,"type":null},"content":[{"type":"text","text":"Elastic Metaspace"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"改进;还有一个新的Java应用程序"},{"type":"link","attrs":{"href":"https:\/\/docs.oracle.com\/en\/java\/javase\/16\/jpackage\/packaging-overview.html","title":null,"type":null},"content":[{"type":"text","text":"打包工具"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":",允许你为Windows、Mac和Linux创建原生安装程序。最后,当你从classpath运行应用程序时,JDK中的封装类型将受到严格保护,我认为这也会有很大影响。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"我强烈建议你研究所有这些新特性和语言增强,因为其中一些改进会对你的应用程序产生重大影响。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"作者介绍"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/twitter.com\/Sander_Mak","title":null,"type":null},"content":[{"type":"text","text":"Sander Mak"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]},{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":"是一名Java Champion,在Java社区活跃了十多年。目前他是Picnic的技术总监。同时,Mal也经常做知识分享,通过各种会议和在线电子学习平台传授经验。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}],"text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}},{"type":"strong"}],"text":"原文链接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.infoq.com\/articles\/java-16-new-features\/","title":null,"type":null},"content":[{"type":"text","text":"What's New in Java 16"}],"marks":[{"type":"color","attrs":{"color":"#494949","name":"user"}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章