Java 14 新功能搶先看!

Java 14 新功能搶先看!

第14版包含的JEP(Java Enhancement Proposals,Java增強提案)比12版和13版加起來還要多。在這篇文章中,我將主要討論以下幾點:

  • 改進的switch表達式,第一次出現在Java 12和13中,在Java 14中獲得了完全的支持
  • instanceof支持模式匹配(語言特性)
  • NullPointerException(JVM特性)

希望你在閱讀完本文後,積極地代碼中實驗這些功能,為Java團隊提供反饋,併為Java的發展做出貢獻。

一、Switch表達式

java 14 中的switch表達式將會永久存在。在之前的發佈中,switch表達式只是一個“預覽”階段的特性。我想提醒一下,“預覽”階段的特性的目的是為了收集反饋,這些特性可能會隨時改變,根據反饋結果,這些特性甚至可能會被移除,但通常所有的預覽特性最後都會在java中固定下來。

新的switch表達式的優點是,不再有缺省跳過行為(fall-through),更全面,而且表達式和組合形式更容易編寫,因此出現bug的可能性就更低。例如,switch表達式現在可以使用箭頭語法,如下所示:

<code>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;
}
};
/<code>

二、文本塊

Java 13引入的一個預覽功能是文本塊。有了文本塊,多行的字符串字面量就很容易編寫了。這個功能在Java 14中進行第二次預覽,而且發生了一些變化。例如,多行文本的格式化可能需要編寫許多字符串連接操作和轉義序列。下面的代碼演示了一個HTML的例子:

<code>String html = "" +
"\\n\\t" + "" +
"\\n\\t\\t" + "

\"Java 14 is here!\"

" +
"\\n\\t" + "" +
"\\n" + "";
/<code>

有了文本塊,就可以簡化這一過程,只需使用三引號作為文本塊的起始和結束標記,就能編寫出更優雅的代碼:

<code>String html = """


"Java 14 is here!"



""";

/<code>

與普通字符串字面量相比,文本塊的表達性更好。Java 14引入了兩個新的轉義序列。第一,可以使用新的 \\s 轉義序列來表示一個空格。第二,可以使用反斜槓 \\ 來避免在行尾插入換行字符。這樣可以很容易地在文本塊中將一個很長的行分解成多行來增加可讀性。

例如,現在編寫多行字符串的方式如下:

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

在文本塊中使用 \\ 轉義序列,就可以寫成這樣:

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

三、instanceof的模式匹配

Java 14引入了一個預覽特性,有了它就不再需要編寫先通過instanceof判斷再強制轉換的代碼了。例如,下面的代碼:

<code>if (obj instanceof Group) {
Group group = (Group) obj;

// use group specific methods
var entries = group.getEntries();
}
/<code>

利用這個預覽特性可以重構為:

<code>if (obj instanceof Group group) {
var entries = group.getEntries();
}
/<code>

由於條件檢查要求obj為Group類型,為什麼還要像第一段代碼那樣在條件代碼塊中指明obj為Group類型呢?這可能會引發錯誤。

這種更簡潔的語法可以去掉Java程序裡的大多數強制類型轉換。(2011年的一篇針對相關語言特性的研究論文(http://www.cs.williams.edu/FTfJP2011/6-Winther.pdf)指出,24%的類型轉換都來自於instanceof後的條件語句。)

JEP 305解釋了這項改變,並給出了Joshuoa Bloch的著作《Effective Java》中的一個例子,演示了下面兩種等價的寫法:

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

這段代碼中冗餘的CaseInsensitiveString強制類型轉換可以去掉,轉換成下面的方式:

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

這個預覽特性很值得嘗試,因為它打開了通向更通用的模式匹配的大門。模式匹配的思想是為語言提供一個便捷的語法,根據特定的條件從對象中提取出組成部分。這正是instanceof操作符的用例,因為條件就是類型檢查,提取操作需要調用適當的方法,或訪問特定的字段。

換句話說,該預覽功能僅僅是個開始,以後該功能肯定能夠減少更多的代碼冗餘,從而降低bug發生的可能性。

四、Record

另一個預覽功能就是record。與前面介紹的其他預覽功能一樣,這個預覽功能也順應了減少Java冗餘代碼的趨勢,能幫助開發者寫出更精準的代碼。Record主要用於特定領域的類,它的位移功能就是存儲數據,而沒有任何自定義的行為。

我們開門見山,舉一個最簡單的領域類的例子:BankTransaction,它表示一次交易,包含三個字段:日期,金額,以及描述。定義類的時候需要考慮多個方面:

構造器 getter方法 toString() hashCode()和equals()

這些部分的代碼通常由IDE自動生成,而且會佔用很大篇幅。下面是生成的完整的BankTransaction類:

<code>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);
}
}
/<code>

Java 14提供了一種方法可以解決這種冗餘,可以更清晰地表達目的:這個類的唯一目的就是將數據整合在一起。Record會提供equals、hashCode和toString方法的實現。因此,BankTransaction類可以重構如下:

<code>public record BankTransaction(LocalDate date,
double amount,
String description) {}
/<code>

通過record,可以“自動”地得到equals,hashCode和toString的實現,還有構造器和getter方法。

要想嘗試這個例子,需要用preview標誌編譯該文件: javac --enable-preview --release 14 BankTransaction.java

record的字段隱含為final。因此,record的字段不能被重新賦值。但要注意的是,這並不代表整個record是不可變的,保存在字段中的對象可以是可變的。

如果你有興趣閱讀更多關於record的內容,可以閱讀Ben Evans最近在《Java Magazine》上發表的文章(https://blogs.oracle.com/javamagazine/records-come-to-java)。

請繼續關注該功能。從培養新一代的Java開發者的視角來看,Record也很有意思。例如,如果你要培養初級開發者,那麼record應該什麼時候講呢?是在講OOP之前還是之後?

五、NullPointerException

一些人認為,拋出NullPointerException異常應該當做新的“Hello World”程序來看待,因為NullPointerException是早晚會遇到的。玩笑歸玩笑,這個異常的確會造成困擾,因為它經常出現在生產環境的日誌中,會導致調試非常困難,因為它並不會顯示原始的代碼。例如,如下代碼:

<code>var name = user.getLocation().getCity().getName();
/<code>

在Java 14之前,你可能會得到如下的錯誤:

<code>Exception in thread "main" java.lang.NullPointerException
at NullPointerExample.main(NullPointerExample.java:5)
/<code>

不幸的是,如果在第5行是一個包含了多個方法調用的賦值語句(如getLocation()和getCity()),那麼任何一個都可能會返回null。實際上,變量user也可能是null。因此,無法判斷是誰導致了NullPointerException。

在Java 14中,新的JVM特性可以顯示更詳細的診斷信息:

<code>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)
/<code>

該消息包含兩個明確的組成部分

  • 後果:Location.getCity()無法被調用
  • 原因:User.getLocation()的返回值為null

增強版本的診斷信息只有在使用下述標誌運行Java時才有效:

<code>-XX:+ShowCodeDetailsInExceptionMessages
/<code>

下面是個例子:

<code>java -XX:+ShowCodeDetailsInExceptionMessages NullPointerExample
/<code>

在以後的版本中,該選項可能會成為默認。

這項改進不僅對於方法調用有效,其他可能會導致NullPointerException的地方也有效,包括字段訪問、數組訪問、賦值等。

六、總結

Java 14提供了幾個新的預覽版語言特性和更新,能很好地幫助開發者完成日常工作。Java 14還引入了record,這是一種創建精確數據類的新方法。此外,NullPointerException的消息經過了改進,能顯示明確的診斷信息。switch表達式也成了Java 14的一部分。文本塊功能可以幫你處理多行字符串,這是在引入了兩個新的轉義序列之後的另一預覽功能。還有一項改動就是JDK Flight Recorder的事件流。

可見,Java 14帶來了許多創新。你應該嘗試一下這些功能,然後反饋給Java的開發團隊。


分享到:


相關文章: