從 Java 8 遷移到 Java 17
一、概述
我們經常面臨是遷移到更新版本的 Java 還是繼續使用現有版本的兩難選擇。換句話說,我們需要平衡新功能和增強功能與遷移所需的總工作量。
在本教程中,我們將介紹新版 Java 中提供的一些非常有用的功能。這些特性不僅易於學習,而且在計劃從 Java 8 遷移到 Java 17 時也可以毫不費力地快速實現。
2.使用String
讓我們看一下String
類的一些有趣的增強功能。
2.1.緊湊的字符串
Java 9 引入了 Compact String,這是一種性能增強功能,可以優化String
對象的內存消耗。
簡單來說, String
對象將在內部表示為byte[]
而不是char[]
。解釋一下,每個char
由 2 個字節組成,因為 Java 內部使用 UTF-16 。在大多數情況下, String
對像是可以用單個byte
表示的英語單詞,它只不過是 LATIN-1 表示法。
Java 根據String
對象的實際內容在內部處理此表示:一個byte[]
用於 LATIN-1 字符集,一個char[]
如果內容包含任何特殊字符 (UTF-16)。因此,它對使用String
對象的開發人員來說是完全透明的。
在 Java 8 之前, String
在內部表示為char[]
:
char[] value;
從 Java 9 開始,它將是一個byte[]
:
byte[] value;
2.2.文本塊
在 Java 15 之前,嵌入多行代碼片段需要明確的行終止符、 String
連接和定界符。為了解決這個問題, Java 15 引入了文本塊,允許我們或多或少地按原樣嵌入代碼片段和文本序列。這在處理 HTML、JSON 和 SQL 等文字片段時特別有用。
文本塊是String
表示的另一種形式,可以在任何可以使用普通雙引號String
文字的地方使用。例如,可以在不顯式使用行終止符和String
連接的情況下表示多行String
文字:
// Using a Text Block
String value = """
Multi-line
Text
""";
在此功能之前,多行文本的可讀性不強,表示起來也很複雜:
// Using a Literal String
String value = "Multi-line"
+ "\n" \\ line separator
"Text"
+ "\n";
String str = "Multi-line\nText\n";
2.3.新的String
方法
在處理String
對象時,我們往往傾向於使用 Apache Commons 等第三方庫來進行常見的String
操作。具體來說,這是實用程序函數檢查空白/空值和其他String
操作(如重複、縮進等)的情況。
隨後,Java 11 和 Java 12 引入了許多這樣方便的函數,這樣我們就可以依靠內置函數來進行常規的String
操作: isBlank(), repeat(), indent(), lines(), strip(),
和transform().
讓我們看看他們的行動:
assertThat(" ".isBlank());
assertThat("Twinkle ".repeat(2)).isEqualTo("Twinkle Twinkle ");
assertThat("Format Line".indent(4)).isEqualTo(" Format Line\n");
assertThat("Line 1 \n Line2".lines()).asList().size().isEqualTo(2);
assertThat(" Text with white spaces ".strip()).isEqualTo("Text with white spaces");
assertThat("Car, Bus, Train".transform(s1 -> Arrays.asList(s1.split(","))).get(0)).isEqualTo("Car");
3.記錄
數據傳輸對象 (DTO) 在對象之間傳遞數據時很有用。但是,創建 DTO 會附帶很多樣板代碼,例如字段、構造函數、getter/setter、 equals()
、 hashcode()
和toString()
方法:
public class StudentDTO {
private int rollNo;
private String name;
// constructors
// getters & setters
// equals(), hashcode() & toString() methods
}
輸入record
類,這是一種特殊的類,可以以更緊湊的方式定義不可變數據對象,並且與 Project Lombok 相同。 record
類最初作為 Java 14 的預覽功能引入,是 Java 16 的標準功能:
public record Student(int rollNo, String name) {
}
如我們所見, record
類只需要字段的類型和名稱。隨後,除了public
構造函數、 private
字段和final
字段之外,編譯器還生成equals()
、 hashCode()
) 和toString()
方法:
Student student = new Student(10, "Priya");
Student student2 = new Student(10, "Priya");
assertThat(student.rollNo()).isEqualTo(10);
assertThat(student.name()).isEqualTo("Priya");
assertThat(student.equals(student2));
assertThat(student.hashCode()).isEqualTo(student2.hashCode());
4. 有用的NullPointerException
NullPointerException
(NPE) 是每個開發人員都會遇到的非常常見的異常。在大多數情況下,編譯器拋出的錯誤消息對於識別為null
的確切對像沒有用。此外,近來函數式編程和方法鏈的趨勢使得編寫代碼更加簡潔明了,這使得調試 NPE 變得更加困難。
讓我們看一個使用方法鏈的例子:
student.getAddress().getCity().toLowerCase();
在這裡,如果在這一行中拋出 NPE,則很難精確定位null
對象的確切位置,因為三個可能的對像都可能是null
。
從 Java 14 開始,我們現在可以使用額外的 VM 參數指示編譯器獲取有用的 NPE 消息:
-XX:+ShowCodeDetailsInExceptionMessages
啟用此選項後,錯誤消息會更加準確:
Cannot invoke "String.toLowerCase()" because the return value of "com.baeldung.java8to17.Address.getCity()" is null
5. 模式匹配
模式匹配解決了程序中的一個通用邏輯,即有條件地從對像中提取組件,以更簡潔和安全地表達。
讓我們看一下 Java 中支持模式匹配的兩個特性。
5.1.增強的instanceOf
運算符
每個程序都有一個共同的邏輯是檢查某個類型或結構並將其轉換為所需的類型以執行進一步的處理。這涉及很多樣板代碼。
讓我們看一個例子:
if (obj instanceof Address) {
Address address = (Address) obj;
city = address.getCity();
}
在這裡,我們可以看到涉及三個步驟:測試(確認類型)、轉換(轉換為特定類型)和新局部變量(進一步處理)。
從 Java 16 開始, instanceof
運算符的模式匹配是解決此問題的標準功能。現在,我們可以以更具可讀性的方式直接訪問目標類型:
if (obj instanceof Address address) {
city = address.getCity();
}
5.2.切換錶達式
開關表達式 (Java 14) 類似於計算或返回單個值並可在語句中使用的正則表達式。此外,Java 17 使我們能夠在 Switch 表達式中使用模式匹配(預覽功能):
double circumference = switch(shape) {
case Rectangle r -> 2 * r.length() + 2 * r.width();
case Circle c -> 2 * c.radius() * Math.PI;
default -> throw new IllegalArgumentException("Unknown shape");
};
正如我們所注意到的,有一種新的case
標籤語法。 switch
表達式使用“ case L ->
”標籤而不是“ case
L:
”標籤。此外,不需要明確的break
語句來防止失敗。此外, switch
選擇器表達式可以是任何類型。
在 Switch 表達式中使用傳統的“ case
L:
”標籤時,我們必須使用yield
關鍵字(而不是break
語句)來返回值。
6.密封類
繼承的主要目的是代碼的可重用性。然而,某些業務領域模型可能只需要一組預定義的類來擴展基類或接口。這在使用領域驅動設計時特別有價值。
為了增強這種行為,Java 17 提供了密封類作為標準功能。簡而言之,一個sealed
類或接口只能由那些被允許這樣做的類和接口來擴展或實現。
讓我們看看如何定義sealed
類:
public sealed class Shape permits Circle, Square, Triangle {
}
在這裡, Shape
類只允許繼承一組受限制的類。此外,允許的子類必須定義以下修飾符之一: final
、 sealed
或**non-sealed**
:
public final class Circle extends Shape {
public float radius;
}
public non-sealed class Square extends Shape {
public double side;
}
public sealed class Triangle extends Shape permits ColoredTriangle {
public double height, base;
}
七、結論
多年來,Java 從 Java 8 (LTS) 到 Java 17 (LTS) 分階段引入了一堆新特性。這些特性中的每一個都旨在提高多個方面,例如生產力、性能、可讀性和可擴展性。
在本文中,我們探討了一組精選的可快速學習和實施的功能。具體來說,對String
類、 record
類型和模式匹配的改進將使從 Java 8 遷移到 Java 17 的開發人員的生活更加輕鬆。
與往常一樣,本文中使用的所有代碼示例都可以在 GitHub 上找到。