Categories
程式開發

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 = "" +
"nt" + "" +
"ntt" + "

"Java 14 is here!"

" + "nt" + "" + "n" + "";

有了文本塊特性之後,可以使用三引號來表示字符串的開頭和結尾,這樣的代碼看起來更簡潔、更優雅:

String html = """

  
    

"Java 14 is here!"

""";

在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