Java 14将于3月17号发布,新特性一览

Java 14计划于3月17号发布。这一版本包含的JEP比Java 12和Java 13的总和还要多。那么,对于每天需要面对Java代码的开发者来说,哪些东西最值得关注?

本文将着重介绍以下这些Java新特性:

  • 改进的switch表达式。这一特性已经作为预览版出现在Java 12和Java 13中,而Java 14将带来它的完整正式版。
  • instanceof的模式匹配(这是个一语言特性)。
  • 非常有用的NullPointerException信息(这是一个JVM特性)。

switch表达式

在Java 14中,switch表达式是一个正式的特性。而在之前的两个Java版本中,这个特性只是预览版。设定“预览版”的目的是为了收集开发者反馈,并根据反馈结果决定相应的特性是否要做出修改,甚至是移除,但其中的大部分都会成为正式特性。

新的switch表达式有助于减少bug,因为它的表达和组合方式更容易编写。例如,下面的示例使用了箭头语法:

var log = switch (event) {
    case PLAY -> "User has triggered the play button";
    case STOP, PAUSE -> "User needs a break";
    default -> {
        String message = event.toString();
        LocalDateTime now = LocalDateTime.now();
        yield "Unknown event " + message + 
              " logged on " + now;
    }
};

文本块

Java 13引入了文本块特性,并将其作为预览版。有了这个特性,处理多行字符串字面量就容易了很多。在Java 14中,该特性仍然是预览版,不过做了一些调整。在没有这个特性之前,要表示多行格式化的字符串需要像下面这样:

String html = "<HTML>" +
"\n\t" + "<BODY>" +
"\n\t\t" + "<H1>\"Java 14 is here!\"</H1>" +
"\n\t" + "</BODY>" +
"\n" + "</HTML>";

有了文本块特性之后,可以使用三引号来表示字符串的开头和结尾,这样的代码看起来更简洁、更优雅:

String html = """
<HTML>
  <BODY>
    <H1>"Java 14 is here!"</H1>
  </BODY>
</HTML>""";

在Java 14中,该特性增加了两个转义字符。一个是\s,用来表示单空格。一个是反斜杠\,用在行末表示不换行。如果你有一个很长的字符串,为了让代码看起来更好看,但又不希望真的换行,就可以使用这个转义字符。

例如,目前的多行字符串是这样的:

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

使用了新的转义字符之后是这样的:

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

instanceof的模式匹配

为了避免在使用instanceof后还需要进行类型转换,Java 14引入了一个新的预览版特性。例如,在没有该特性之前:

if (obj instanceof Group) {
  Group group = (Group) obj;
  // 调用group的方法
  var entries = group.getEntries();
}

我们可以使用新的特性来重写这段代码:

if (obj instanceof Group group) {
  var entries = group.getEntries();
}

既然条件检查已经确认obj是Group类型,那为什么还要再次进行显式的类型转换呢?这样有可能更容易出错。新的语法可以将代码中的大部分类型转换移除掉。2011年发布的一份研究报告显示,Java代码中有24%的类型转换是跟在instanceof之后的。

Joshua Bloch的经典著作《Effective Java》中有一段代码示例:

@Override public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString) && 
            ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 
}

这段代码可以使用新的语法写成:

@Override public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString cis) &&
            cis.s.equalsIgnoreCase(s); 
}

这个特性很有意思,因为它为更为通用的模式匹配打开了大门。模式匹配通过更为简便的语法基于一定的条件来抽取对象的组件,而instanceof刚好是这种情况,它先检查对象类型,然后再调用对象的方法或访问对象的字段。

记录类(Record)

另一个预览特性是“记录”。该特性主要是为了降低Java语法的“啰嗦”程度,让开发者写出更简洁的代码。这个特性主要用在某些领域类上,这些类主要用于保存数据,不提供领域行为。

我们以一个简单的领域类BankTransaction作为例子,它包含了三个字段:date、amount和description。目前,这个类需要以下几个组件:

  • 构造器;
  • getter方法;
  • toString()方法;
  • hashCode()和equals()方法。

这些方法一般可以通过IDE自动生成,但会占用很大的代码空间,例如:

public class BankTransaction {
    private final LocalDate date;
    private final double amount;
    private final String description;


    public BankTransaction(final LocalDate date, 
                           final double amount, 
                           final String description) {
        this.date = date;
        this.amount = amount;
        this.description = description;
    }

    public LocalDate date() {
        return date;
    }

    public double amount() {
        return amount;
    }

    public String description() {
        return description;
    }

    @Override
    public String toString() {
        return "BankTransaction{" +
                "date=" + date +
                ", amount=" + amount +
                ", description='" + description + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BankTransaction that = (BankTransaction) o;
        return Double.compare(that.amount, amount) == 0 &&
                date.equals(that.date) &&
                description.equals(that.description);
    }

    @Override
    public int hashCode() {
        return Objects.hash(date, amount, description);
    }
}

Java 14提供了一种方式,可以避免这种繁琐的代码,满足开发者希望一个类只是用来聚合数据的意图。BankTransaction可以重构成:

public record BankTransaction(LocalDate date,
                              double amount,
                              String description) {}

除了构造器和getter方法,还会自动生成equals、hashCode和toString方法。

要尝试这个特性,需要在编译代码时打开预览标签:

javac --enable-preview --release 14 BankTransaction.java

记录类的字段隐式都是final的,也就是说不能对它们进行动态赋值。不过要注意,这并不意味着整个记录类对象都是不可变的,如果字段保存的是对象,那么这个对象是可变的。

这里要插一句话,如果从培训的角度来讲,例如你要教会初级开发者,那么记录类应该在什么时候讲授比较好?在介绍OOP和类的概念之前还是之后?

有用的NullPointerException信息

有些人认为,抛出NullPointerException应该成为Java编程的一个新的“Hello world”,因为这是不可避免的。NullPointerException确实让人抓狂,它们经常出现在生产环境的日志里,但调试起来很困难。例如,看看下面这段代码:

var name = user.getLocation().getCity().getName();

这段代码可能会抛出一个异常:

Exception in thread "main" java.lang.NullPointerException
    at NullPointerExample.main(NullPointerExample.java:5)

在一行代码里连续调用了多个方法,比如getLocation()和getCity(),它们都有可能返回null,而user也可能为null。所以,我们无法知道是什么导致了NullPointerException。

在Java 14中,JVM会抛出更多有用的诊断信息:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Location.getCity()" because the return value of "User.getLocation()" is null
    at NullPointerExample.main(NullPointerExample.java:5)

错误信息提供了两个内容:

  • 结果:无法调用Location.getCity()。
  • 原因:User.getLocation()返回值是null。

要启用这个功能,需要添加JVM标识:

-XX:+ShowCodeDetailsInExceptionMessages

例如:

java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample

据报道,在未来的版本中,这个特性可能会默认启用。

这个增强特性不仅适用于方法调用,只要会导致NullPointerException的地方也都适用,包括字段的访问、数组的访问和赋值。

结论

Java 14带来了一些新的预览特性,例如可用于避免显式类型转换的instanceof模式匹配。它还引入了记录类,提供了一种简洁的方式来创建只用于聚合数据的类。另外,增强的NullPointerException错误信息有助于更好地进行诊断。switch表达式成为Java 14的正式特性。文本块进入第二轮预览,新增了两个转义字符。

英文原文

Java 14 arrives with a host of new features

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/Lt8JfYzeDYgxsUfaNdvY
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券