下列Java增强建议(JEP)已经实现。在本文中,我们将从开发人员的角度来仔细研究感兴趣的主题。

JEP 305: Pattern Matching for instanceof (Preview)
JEP 343: Packaging Tool (Incubator)
JEP 345: NUMA-Aware Memory Allocation for G1
JEP 349: JFR Event Streaming
JEP 352: Non-Volatile Mapped Byte Buffers
JEP 358: Helpful NullPointerExceptions
JEP 359: Records (Preview)
JEP 361: Switch Expressions (Standard)
JEP 362: Deprecate the Solaris and SPARC Ports
JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
JEP 364: ZGC on macOS
JEP 365: ZGC on Windows
JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination
JEP 367: Remove the Pack200 Tools and API
JEP 368: Text Blocks (Second Preview)
JEP 370: Foreign-Memory Access API (Incubator)

JEP 305: Pattern matching for instanceof

自20世纪60年代以来,模式匹配语言的概念已经在各种编程语言中得到了应用。其中最现代的例子是 Haskell 和 Scala。模式是一个谓词的组合,该谓词匹配目标结构和该模式中的一组变量。如果这些变量匹配,则为它们分配相应的内容。其目的是破坏对象,也就是将它们分解为它们的组件。

到目前为止,Java 只能区分 switch 语句中的数据类型 integer、 string 和 enum。然而,随着 Java 12中开关表达式的引入,迈向模式匹配的第一步已经迈出。使用 Java 14,我们现在可以额外地使用模式匹配操作符 instanceof。避免了不必要的强制转换,减少的冗余提高了可读性。
例如,在此之前,必须按照以下步骤检查空字符串或空集合:

  1. boolean isNullOrEmpty( Object o ) {
  2. return == null ||
  3. instanceof String && ((String) o).isBlank() ||
  4. instanceof Collection && ((Collection) o).isEmpty();
  5. }

现在您可以在使用 instanceof 检查时直接将值赋给变量,并对其执行进一步调用:

  1. boolean isNullOrEmpty( Object o ) {
  2. return o == null ||
  3. o instanceof String s && s.isBlank() ||
  4. o instanceof Collection c && c.isEmpty();
  5. }

这种区别似乎微不足道,然而,Java 开发人员中的纯粹主义者节省了一个小而烦人的冗余。
开关表达式最早是在 Java 12和13中引入的,在这两种情况下都是作为一个预览特性。它们现已在 jep361中最后确定。这为开发人员提供了两种新的语法变体,它们具有更短、更清晰和更不容易出错的语义。表达式的结果可以分配给变量,或者作为方法的值返回(清单1)。

JEP 358: Helpful NullPointerExceptions

对空引用的无意访问也是 Java 开发人员所担心的。根据托尼•霍尔爵士(Sir Tony Hoare)自己的说法,他发明的零Y引用是一个错误,其后果高达数十亿美元。这仅仅是因为在20世纪60年代阿尔戈语的发展过程中,它是如此容易实现。
在 Java 中,编译器和运行时环境都不支持处理零引用。这些恼人的异常可以通过各种变通方法来避免。最简单的方法是将检查设置为零。不幸的是,这个过程非常繁琐,当我们需要它的时候我们往往会忘记它。
使用自 JDK 8以来包含的包装器类 Optional,您可以通过 API 显式地告诉调用者,一个值可以为零,并且它必须对此进行响应。因此,您不能再意外地遇到空引用,而必须显式地处理可能为空的值。这个过程对于公共接口的返回值非常有用,但是也会消耗额外的间接层,因为您总是需要解压实际值。
在其他语言中,辅助工具早已构建到语法和编译器中,比如Groovy中 NullObjectPattern 和 Safe Navigation 操作符(some?.method())。在 Kotlin,可以明确区分可能不为空的类型和可能作为引用为 null 的类型。我们将来也必须使用 Java 中的 nullpointerexception。
但是,作为预览特性引入的有用的NullPointerExceptions可以简化异常的故障排除。为了在抛出 NullPointerException 时插入必要的信息,必须在启动时激活选项 -XX: + ShowCodeDetailsInExceptionMessages。如果调用链中的一个值为零,那么您将收到一条有用的消息:

  1. man.partner().name()
  2. Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because the return value of "Person.partner()" is null

Lambda表达式需要特殊处理。例如,如果lambda函数的参数为零,则默认情况下将收到清单2所示的错误消息。要显示正确的参数名称,必须使用-g:vars选项编译源代码。结果如下:

  1. java.lang.NullPointerException: Cannot invoke "Person.name()" because "p" is null

Listing 2 清单2

  1. Stream.of( man, woman )
  2. .map( p -> p.partner() )
  3. .map( p -> p.name() )
  4. .collect( Collectors.toUnmodifiableList() );
  5. Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because "<parameter1>" is null

不幸的是,当一个空参数时,目前没有方法引用的指示:

  1. Stream.of( man, woman )
  2. .map( Person::partner )
  3. .map( Person::name )
  4. .collect( Collectors.toUnmodifiableList() )
  5. Result: java.lang.NullPointerException

但是,如本例所示,如果将每个流方法调用放在新行中,那么麻烦的代码行可以很快地缩小范围。NullPointerExceptions在自动装箱/拆箱中也具有挑战性。如果在这里也激活了编译器参数-g:vars,您还将收到新的有用的错误消息(清单3)。
Listing 3 清单3

  1. int calculate() {
  2. Integer a = 2, b = 4, x = null;
  3. return a + b * x;
  4. }
  5. calculate();
  6. Result: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "x" is null

JEP 359: Records

也许最令人兴奋的同时也是最令人惊讶的创新是记录类型的引入 。它们是在Java 14发行中相对较晚实现的,是一种类声明的限制形式,类似于枚举。
记录是在Valhalla项目中开发的。与Kotlin中的Data Classes和Scala中的Case Classes有某些相似之处。紧凑的语法可能会使Lombok之类的库在将来过时。
Kevlin Henney还看到了以下优点:“我认为Java记录功能的有趣的副作用之一是,实际上,它将帮助揭示多少Java代码实际上是面向 getter / setter而非面向对象的。” 一个人有两个字段的简单定义可以在这里看到 :

  1. public record Person( String name, Person partner ) {}

一个带有附加构造函数的扩展变量,因此只有字段name 是强制的,也可以实现:

  1. public record Person( String name, Person partner ) {
  2. public Person( String name ) { this( name, null ); }
  3. public String getNameInUppercase() { return name.toUpperCase(); }
  4. }

编译器生成一个不可变类,除了这两个属性和它自己的方法之外,它还包含访问器的实现(没有 getter!) 、构造函数 equals / hashcode 和 toString (清单4)。
Listing 4 清单4

  1. public final class Person extends Record {
  2. private final String name;
  3. private final Person partner;
  4. public Person(String name) { this(name, null); }
  5. public Person(String name, Person partner) { this.name = name; this.partner = partner; }
  6. public String getNameInUppercase() { return name.toUpperCase(); }
  7. public String toString() { /* ... */ }
  8. public final int hashCode() { /* ... */ }
  9. public final boolean equals(Object o) { /* ... */ }
  10. public String name() { return name; }
  11. public Person partner() { return partner; }
  12. }

使用的行为符合预期,您无法从调用方判断记录类型是实例化的(清单5)。
Listing 5 清单5

  1. var man = new Person("Adam");
  2. var woman = new Person("Eve", man);
  3. woman.toString(); // ==> "Person[name=Eve, partner=Person[name=Adam, partner=null]]"
  4. woman.partner().name(); // ==> "Adam"
  5. woman.getNameInUppercase(); // ==> "EVE"
  6. // Deep equals
  7. new Person("Eve", new Person("Adam")).equals( woman ); // ==> true

顺便说一下,records 不是经典的 javabean,因为它们不包含真正的 getter。但是,您可以使用相同名称的方法访问成员变量。记录也可以包含注释或 Javadocs。
此外,还可以在主体中声明静态字段、方法、构造函数或实例方法。不允许在记录头之外定义其他实例字段。

JEP 368: Text Blocks

最初计划作为 java12的原始字符串,java13引入了一个更轻量级的版本,称为文本块的多行字符串的形式。特别是对于 HTML 模板和 SQL 脚本,它们极大地提高了可读性(清单6)。
Listing 6 清单6

  1. // 未使用 Text Blocks
  2. String html = "<html>\n" +
  3. " <body>\n" +
  4. "
  5. Hello, Escapes
  6. \n" +
  7. " </body>\n" +
  8. "</html>\n";
  9. // With Text Blocks
  10. String html = """
  11. <html>
  12. <body>
  13. Hello, Text Blocks
  14. </body>
  15. </html>""";Listing 6 清单6
  16. // Without Text Blocks
  17. String html = "<html>\n" +
  18. " <body>\n" +
  19. "
  20. Hello, Escapes
  21. \n" +
  22. " </body>\n" +
  23. "</html>\n";
  24. // 使用 Text Blocks
  25. String html = """
  26. <html>
  27. <body>
  28. Hello, Text Blocks
  29. </body>
  30. </html>""";

还添加了两个新的转义序列,您可以使用它们调整文本块的格式。例如,如果要使用不应在输出中显式出现的换行符,则只需在行尾插入\(反斜杠)即可。这为您提供了一个带有长行的字符串,但是为了清楚起见,您可以在源代码中使用换行符(清单7)。
Listing 7 清单7

  1. String text = """
  2. Lorem ipsum dolor sit amet, consectetur adipiscing \
  3. elit, sed do eiusmod tempor incididunt ut labore \
  4. et dolore magna aliqua.\
  5. """;
  6. // 代替+
  7. String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
  8. "elit, sed do eiusmod tempor incididunt ut labore " +
  9. "et dolore magna aliqua.";

新的转义序列\ s被转换为空格。例如,这可以用于确保行尾的空白不会被自动截断(修剪),并获得每行固定的字符宽度:

  1. String colors = """
  2. red \s
  3. green\s
  4. blue \s
  5. """;

还有什么新鲜事吗?

除了所描述的功能(对于开发人员来说主要是有趣的)之外,还有其他一些更改。在JEP 352中,对FileChannel API进行了扩展,以允许创建MappedByteBuffer实例。与易失性存储器(RAM)不同,它们在非易失性数据存储(NVM,非易失性存储器)上工作。
但是,目标平台是Linux x64。关于垃圾收集也发生了很多事情。并发标记扫描(CMS)垃圾收集器已被删除。因此,ZGC现在也可用于macOS和Windows。
对于关键的Java应用程序,建议在生产中激活飞行记录功能。以下命令使用Flight Recording启动Java应用程序,并将信息写入record.jfr,并始终保留一天的数据:

  1. java \
  2. -XX:+FlightRecorder \
  3. -XX:StartFlightRecording=disk=true, \
  4. filename=recording.jfr,dumponexit=true,maxage=1d \
  5. -jar application.jar

通常,您随后可以使用工具JDK Mission Control(JMC)读取和分析数据。JDK 14的另一个新功能是,您还可以从应用程序异步查询事件(清单8)。
Listing 8 清单8

  1. import jdk.jfr.consumer.RecordingStream;
  2. import java.time.Duration;
  3. try ( var rs = new RecordingStream() ) {
  4. rs.enable( "jdk.CPULoad" ).withPeriod( Duration.ofSeconds( 1 ) );
  5. rs.onEvent( "jdk.CPULoad", event -> {
  6. System.out.printf( "%.1f %% %n", event.getFloat( "machineTotal" ) * 100 );
  7. });
  8. rs.start();
  9. }

在JDK 8中,我们拥有工具javapackager,但是不幸的是,它在版本11中与JavaFX一起从Java中删除。现在,在Java 14中引入了后继jpackage(JEP 343:打包工具),利用它我们可以再次创建独立的Java安装文件。
它们的基础是包括运行时环境的Java应用程序。该工具使用此输入来构建包含所有依赖项的可执行二进制工件(格式:dmg中的msi,exe,pkg,dmg中的app,deb和rpm)。

总结

Java没有死,Java万岁!半年两次的OpenJDK版本使语言和平台都受益。这次,新功能比Java 12和13还要多。而且,仍有许多功能需要在将来的版本中实现。
因此,我们的Java开发人员不会感到无聊,并且未来的前景仍然一片光明。到2020年9月,我们可以预见Java 15的到来。当然不无论,新版任你发,我用 Java 8.