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 的正式特性。文本塊進入第二輪預覽,新增了兩個轉義字符

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章