java –cp

近年来,Oracle做出了一些具有开创性的决定。 它们包括具有预览功能的新半年发布模型,以及针对新功能的较短发布和反馈周期。 许可模式也已更改,不再免费提供Oracle JDK。 这加剧了竞争,因此您现在可以从包括Oracle在内的各种供应商处获得免费的OpenJDK发行版。 从Java 11开始,它就与Oracle JDK二进制兼容,并且处于开源许可之下。

一年半之前,最新的LTS版本Java 11于2019年秋季发布。此后,随后的两个主要发行版仅具有有限的新功能。 但是,JDK孵化器项目(Amber,Valhalla,Loom…)正在研究许多新想法,因此,刚发布的JDK 14的功能范围再次大大扩大也就不足为奇了。 即使只有少数人会在生产中使用新版本,您仍然应该看看这些新功能并尽早提供有关预览功能的反馈。 这是确保新功能可以投入生产的唯一方法,直到在下一个LTS版本中最终确定该版本时为止,该版本将在2021年秋季作为Java 17发布。

还请参见:

已实施以下Java增强建议(JEP)。 在本文中,我们将从开发人员的角度仔细研究有趣的主题。

  • JEP 305:instanceof的模式匹配(预览)
  • JEP 343:包装工具(培养箱)
  • JEP 345:针对G1的NUMA感知内存分配
  • JEP 349:JFR事件流
  • JEP 352:非易失性映射字节缓冲区
  • JEP 358:有用的NullPointerExceptions
  • JEP 359:记录(预览)
  • JEP 361:开关表达式(标准)
  • JEP 362:弃用Solaris和SPARC端口
  • JEP 363:删除并发标记扫描(CMS)垃圾收集器
  • JEP 364:macOS上的ZGC
  • Windows上的JEP 365:ZGC
  • JEP 366:弃用ParallelScavenge + SerialOld GC组合
  • JEP 367:删除Pack200工具和API
  • JEP 368:文本块(第二预览)
  • JEP 370:外部存储器访问API(孵化器)

JEP 305:用于instanceof的模式匹配

自1960年代以来,模式匹配的概念已在各种编程语言中使用。 Haskell和Scala是较现代的示例。 模式是匹配目标结构的谓词和该模式内的一组变量的组合。 如果这些变量匹配,则会分配相应的内容。 目的是破坏对象,即将它们分解为它们的组件。

到目前为止,Java只能区分switch语句中的数据类型integerstringenum 。 但是,随着Java 12中switch表达式的引入,迈向了模式匹配的第一步。 使用Java 14,我们现在可以为instanceof运算符另外使用模式匹配。 避免了不必要的转换,减少的冗余度提高了可读性。

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

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

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

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

差异似乎很小。 但是,Java开发人员中的纯粹主义者节省了少量但令人讨厌的冗余。

开关表达式是在Java 12和13中首次引入的,在两种情况下均作为预览功能。 现在,它们已在JEP 361中完成。这为开发人员提供了两种新的语法变体,它们具有更短,更清晰,更不易出错的语义。 表达式的结果可以分配给变量,也可以从方法中返回值(清单1)。

String developerRating( int numberOfChildren ) {
  return switch (numberOfChildren) {
    case 0 -> "open source contributor";
    case 1, 2 -> "junior";
    case 3 -> "senior";
    default -> {
      if (numberOfChildren < 0) 
        throw new IndexOutOfBoundsException( numberOfChildren );
      yield "manager";
    }
  };
}

JEP 358:有用的NullPointerExceptions

Java开发人员也担心无意访问空引用。 根据托尼·霍尔(Sony Toare Hoare)先生自己的说法,他发明的零基准是一个错误,其后果总计达数十亿美元。 那只是因为它在1960年代开发Algol语言期间非常容易实现。

在Java中,编译器和运行时环境均不支持零引用的处理。 这些烦人的异常可以通过各种解决方法来避免。 最简单的方法是将检查设置为零。 不幸的是,此过程非常繁琐,我们往往会在需要时忘记它。 使用自JDK 8起包含的包装器类Optional,您可以通过API明确告诉调用者该值可以为零,并且它必须对此作出React。 因此,您不必再偶然遇到空引用,而必须显式处理可能为空的值。 此过程对于公共接口的返回值很有用,但由于必须始终解压缩实际值,因此还会花费额外的间接层。

在其他语言中,辅助工具早已内置于语法和编译器中,例如Groovy中的NullObjectPattern和“安全导航”运算符( some?.method() )。 在Kotlin中,可以明确区分可能不是空的类型和可能具有空值作为引用的其他类型。 将来我们也必须在Java中使用NullPointerExceptions。 但是,至少,作为预览功能引入的有用的NullPointerExceptions使故障排除异常更加容易。 为了在引发 NullPointerException时插入必要的信息,必须在启动时激活选项-XX:+ ShowCodeDetailsInExceptionMessages 。 如果呼叫链中的值为零,您将收到一条有用的消息:

man.partner().name()

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

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

java.lang.NullPointerException: Cannot invoke "Person.name()" because "p" is null
Stream.of( man, woman )
  .map( p -> p.partner() )
.map( p -> p.name() )
.collect( Collectors.toUnmodifiableList() );

Result: java.lang.NullPointerException: Cannot invoke "Person.name()" because "<parameter1>" is null

不幸的是,在参数为空的情况下,当前没有方法引用的指示:

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

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

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

JEP 359:记录

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

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

还可以实现带有附加构造函数的扩展变体,以便仅字段名称是强制性的:

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

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

public final class Person extends Record {
  private final String name;
  private final Person partner;
  
  public Person(String name) { this(name, null); }
  public Person(String name, Person partner) { this.name = name; this.partner = partner; }

  public String getNameInUppercase() { return name.toUpperCase(); }
  public String toString() { /* ... */ }
  public final int hashCode() { /* ... */ }
  public final boolean equals(Object o) { /* ... */ }
  public String name() { return name; }
  public Person partner() { return partner; }
}

该用法的行为符合预期。 您无法从调用者那里得知记录类型是实例化的(清单5)。

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

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

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

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

JEP 368:文本块

Java 13最初被计划为Java 12的原始字符串文字,Java 13以称为文本块的多行字符串的形式引入了较轻的版本。 特别是对于HTML模板和SQL脚本,它们极大地提高了可读性(清单6)。

// Without Text Blocks
String html = "<html>\n" +
              "    <body>\n" +
              "        

Hello, Escapes

\n" +
              "    </body>\n" +
              "</html>\n";

// With Text Blocks
String html = """
              <html>
                  <body>
                      

Hello, Text Blocks

                  </body>
              </html>""";

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

String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;
// instead of
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
                 "elit, sed do eiusmod tempor incididunt ut labore " +
                 "et dolore magna aliqua.";

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

String colors = """
    red  \s
    green\s
    blue \s
    """;

还有什么是新的?

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

对于关键的Java应用程序,建议在生产中激活飞行记录功能。 以下命令使用Flight Recording启动Java应用程序,并将信息写入record.jfr ,始终保留一天的数据:

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

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

import jdk.jfr.consumer.RecordingStream;
import java.time.Duration;

try ( var rs = new RecordingStream() ) {
  rs.enable( "jdk.CPULoad" ).withPeriod( Duration.ofSeconds( 1 ) );
  rs.onEvent( "jdk.CPULoad", event -> {
    System.out.printf( "%.1f %% %n", event.getFloat( "machineTotal" ) * 100 );
  });
  rs.start();
}

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

还请参见:

结论

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

翻译自: https://jaxenter.com/java-jdk-14-features-169922.html

java –cp