vlambda博客
学习文章列表

JDK 14 新特性,正式来啦!

每天为您推送优质技术文章


近年来,甲骨文做出了一些突破性的决定。 他们包括新的半年发布模式与预览功能和更短的发布和反馈周期的新功能。 许可模式也发生了变化,Oracle JDK 不再免费提供。 这加剧了竞争,因此您现在可以从包括 Oracle 在内的各种供应商获得免费的 OpenJDK 发行版。 自从 java11以来,它已经与 oraclejdk 实现了二进制兼容,并且采用开源许可证。

一年半以前,最后一个 LTS 版本 Java 11于2019年秋季发布。 从那时起,随后的两个主要版本只有有限数量的新特性。 JDK 孵化器项目(Amber, Valhalla, Loom …) ,然而,正在致力于许多新想法,所以不足为奇的是,刚刚发布的 JDK 14的功能范围再次显著扩大。 即使只有少数人会在生产环境中使用新版本,您仍然应该关注新特性,并尽早就预览功能提供反馈。 这是确保新特性投入生产直到下一个 LTS 版本最终完成的唯一方法,该版本将在2021年秋季以 Java 17的形式发布。

下列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 语句中的数据类型 integerstringenum。 然而,随着 Java 12中开关表达式的引入,迈向模式匹配的第一步已经迈出。 使用 Java 14,我们现在可以额外地使用模式匹配操作符 i nstanceof。 避免了不必要的强制转换,减少的冗余提高了可读性。

例如,在此之前,必须按照以下步骤检查空字符串或空集合:

       
         
         
       
  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中 NullObjectPatternSafeNavigation 操作符(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所示的错误消息。要显示正确的参数名称,必须使用 -gvars选项编译源代码。 结果如下:

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

       
         
         
       
  1. Listing 2 清单2

  2. Stream.of( man, woman )

  3.  .map( p -> p.partner() )

  4. .map( p -> p.name() )

  5. .collect( Collectors.toUnmodifiableList() );

  6. 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在自动装箱/拆箱中也具有挑战性。 如果在这里也激活了编译器参数 -gvars,您还将收到新的有用的错误消息(清单3)。

       
         
         
       
  1. Listing 3 清单3

  2. int calculate() {

  3.  Integer a = 2, b = 4, x = null;

  4.  return a + b * x;

  5. }

  6. calculate();

  7. Result: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "x" is null

JEP 359: Records

也许最令人兴奋的同时也是最令人惊讶的创新是记录类型的引入 。 它们是在Java 14发行中相对较晚实现的,是一种类声明的限制形式,类似于枚举。 记录是在 Valhalla项目中开发的。 与 Kotlin中的 DataClassesScala中的 CaseClasses有某些相似之处。 紧凑的语法可能会使 Lombok之类的库在将来过时。 KevlinHenney还看到了以下优点:“我认为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)。

       
         
         
       
  1. Listing 4 清单4

  2. public final class Person extends Record {

  3.  private final String name;

  4.  private final Person partner;

  5.  public Person(String name) { this(name, null); }

  6.  public Person(String name, Person partner) { this.name = name; this.partner = partner; }

  7.  public String getNameInUppercase() { return name.toUpperCase(); }

  8.  public String toString() { /* ... */ }

  9.  public final int hashCode() { /* ... */ }

  10.  public final boolean equals(Object o) { /* ... */ }

  11.  public String name() { return name; }

  12.  public Person partner() { return partner; }

  13. }

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

       
         
         
       
  1. Listing 5 清单5

  2. var man = new Person("Adam");

  3. var woman = new Person("Eve", man);

  4. woman.toString(); // ==> "Person[name=Eve, partner=Person[name=Adam, partner=null]]"

  5. woman.partner().name(); // ==> "Adam"

  6. woman.getNameInUppercase(); // ==> "EVE"

  7. // Deep equals

  8. new Person("Eve", new Person("Adam")).equals( woman ); // ==> true

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

JEP 368: Text Blocks

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

       
         
         
       
  1. Listing 6 清单6

  2. // 未使用 Text Blocks

  3. String html = "<html>\n" +

  4.              "    <body>\n" +

  5.              "        

  6. Hello, Escapes

  7. \n" +

  8.              "    </body>\n" +

  9.              "</html>\n";

  10. // With Text Blocks

  11. String html = """

  12.              <html>

  13.                  <body>

  14. Hello, Text Blocks

  15.                  </body>

  16.              </html>""";Listing 6 清单6

  17. // Without Text Blocks

  18. String html = "<html>\n" +

  19.              "    <body>\n" +

  20.              "        

  21. Hello, Escapes

  22. \n" +

  23.              "    </body>\n" +

  24.              "</html>\n";

  25. // 使用 Text Blocks

  26. String html = """

  27.              <html>

  28.                  <body>

  29. Hello, Text Blocks

  30.                  </body>

  31.              </html>""";

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

       
         
         
       
  1. Listing 7 清单7

  2. String text = """

  3.                Lorem ipsum dolor sit amet, consectetur adipiscing \

  4.                elit, sed do eiusmod tempor incididunt ut labore \

  5.                et dolore magna aliqua.\

  6.                """;

  7. // 代替+

  8. String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +

  9.                 "elit, sed do eiusmod tempor incididunt ut labore " +

  10.                 "et dolore magna aliqua.";

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

       
         
         
       
  1. String colors = """

  2.    red  \s

  3.    green\s

  4.    blue \s

  5.    """;

还有什么新鲜事吗?

除了所描述的功能(对于开发人员来说主要是有趣的)之外,还有其他一些更改。 在 JEP352中,对 FileChannelAPI进行了扩展,以允许创建 MappedByteBuffer实例。 与易失性存储器( RAM)不同,它们在非易失性数据存储( NVM,非易失性存储器)上工作。 但是,目标平台是 Linuxx64。 关于垃圾收集也发生了很多事情。 并发标记扫描( CMS)垃圾收集器已被删除。 因此, ZGC现在也可用于 macOSWindows

对于关键的 Java应用程序,建议在生产中激活飞行记录功能。 以下命令使用 FlightRecording启动 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

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

       
         
         
       
  1. Listing 8 清单8

  2. import jdk.jfr.consumer.RecordingStream;

  3. import java.time.Duration;

  4. try ( var rs = new RecordingStream() ) {

  5.  rs.enable( "jdk.CPULoad" ).withPeriod( Duration.ofSeconds( 1 ) );

  6.  rs.onEvent( "jdk.CPULoad", event -> {

  7.    System.out.printf( "%.1f %% %n", event.getFloat( "machineTotal" ) * 100 );

  8.  });

  9.  rs.start();

  10. }

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

总结

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

jaxenter.com/java-jdk-14-features-169922.html


长按二维码关注Java技术专栏

点个在看再走呗!