JDK21新特性Record Patterns記錄模式詳解

来源:https://www.cnblogs.com/JavaEdge/archive/2023/09/25/17728167.html
-Advertisement-
Play Games

1 摘要 通過使用記錄模式來增強Java編程語言,以解構記錄值。記錄模式和類型模式可嵌套使用,從而實現強大、聲明式和可組合的數據導航和處理形式。 2 發展史 由 JEP 405 提出的預覽功能,併在JDK 19發佈,然後由 JEP 432 再次預覽,併在JDK 20發佈。該功能與用於switch的模 ...


1 摘要

通過使用記錄模式來增強Java編程語言,以解構記錄值。記錄模式和類型模式可嵌套使用,從而實現強大、聲明式和可組合的數據導航和處理形式。

2 發展史

JEP 405 提出的預覽功能,併在JDK 19發佈,然後由 JEP 432 再次預覽,併在JDK 20發佈。該功能與用於switch的模式匹配(JEP 441)共同演進,並且二者有相當大的交互作用。本JEP提議在持續的經驗和反饋基礎上對該功能完善。

除了一些次要的編輯更改,自第二個預覽版以來的主要變化是刪除了對增強for語句頭部出現記錄模式的支持。這個功能可能會在未來的JEP中重提。

3 目標

  • 擴展模式匹配以解構記錄類的實例,實現更複雜的數據查詢
  • 添加嵌套模式,實現更可組合的數據查詢

4 動機

Java 16中, JEP 394 擴展了instanceof運算符,使其可接受類型模式並執行模式匹配。這個簡單的擴展使得熟悉的instanceof和強制轉換慣用法變得更簡潔、更不易出錯:

// <Java 16
if (obj instanceof String) {
    String s = (String)obj;
    ... 使用s ...
}
// ≥Java 16
if (obj instanceof String s) {
    ... 使用s ...
}

新代碼中,若obj在運行時是String的實例,則obj與類型模式String s匹配。若模式匹配成功,則instanceof true,且模式變數s被初始化為obj強制轉換為String的值,然後可以在包含的代碼塊中使用。

類型模式一次性消除了許多類型轉換的出現。然而,它們只是朝著更聲明式、以數據為焦點的編程風格邁出的第一步。隨Java支持新的、更具表現力的數據建模,模式匹配可通過讓開發表達模型的語義意圖來簡化對這些數據的使用。

5 Pattern matching和records

記錄 (JEP 395) 是數據的透明載體。接收記錄類實例的代碼通常會使用內置的組件訪問器方法提取數據,即組件。

5.1 Point的實例

如用類型模式測試一個值是否是記錄類Point的實例,併在匹配成功時從該值中提取x和y組件。

Java8

class Point {
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
}

static void printSum(Object obj) {
    if (obj instanceof Point) {
        Point p = (Point) obj;
        int x = p.getX();
        int y = p.getY();
        System.out.println(x + y);
    }
}

≥Java 16

record Point(int x, int y) {}

static void printSum(Object obj) {
    if (obj instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x+y);
    }
}

僅使用模式變數p調用訪問方法x()、y(),這些方法返回組件x和y的值。

在每個記錄類中,其訪問方法和組件之間存在一對一對應關係。

如果模式不僅可測試一個值是否是Point的實例,還可直接從該值中提取x和y組件,從而代表我們調用訪問器方法的意圖將更好。換句話說:

// Java 21及以後
static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}

Point(int x, int y) 是一個record pattern。它將用於提取組件的局部變數的聲明直接提升到模式本身,併在值與模式匹配時通過調用訪問方法對這些變數初始化。實際上,record pattern將記錄的實例解構為其組件。

6 嵌套record pattern

模式匹配的真正威力在於優雅擴展到匹配更複雜的對象圖。

考慮以下聲明:

// Java 16及以後
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

已知可使用記錄模式提取對象的組件。如想從左上角點提取顏色:

// Java 21及以後
static void printUpperLeftColoredPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
         System.out.println(ul.c());
    }
}

但ColoredPoint值ul本身是個記錄值,希望進一步分解。因此,記錄模式支持嵌套,允許對記錄組件進一步匹配、分解。可在記錄模式中嵌套另一個模式,同時對外部和內部記錄分解:

// Java 21及以後
static void printColorOfUpperLeftPoint(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
                               ColoredPoint lr)) {
        System.out.println(c);
    }
}

嵌套模式允許以與組裝對象的代碼一樣清晰簡潔方式拆解聚合。如創建一個矩形,通常會將構造函數嵌套在一個表達式中:

// Java 16及以後
Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), 
                            new ColoredPoint(new Point(x2, y2), c2));

使用嵌套模式,我們可以使用與嵌套構造函數結構相似的代碼來解構這樣的矩形:

// Java 21及以後
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
    if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
                               var lr)) {
        System.out.println("Upper-left corner: " + x);
    }
}

嵌套模式可能無法匹配:

// Java 21及以後
record Pair(Object x, Object y) {}
Pair p = new Pair(42, 42);
if (p instanceof Pair(String s, String t)) {
    System.out.println(s + ", " + t);
} else {
    System.out.println("Not a pair of strings");
}

這裡的記錄模式Pair(String s, String t)包含了兩個嵌套的類型模式,即String s和String t。如果一個值與模式Pair(String s, String t)匹配,那麼它是一個Pair,並且遞歸地,它的組件值與類型模式String s和String t匹配。在我們上面的示例代碼中,由於記錄的兩個組件值都不是字元串,因此這些遞歸的模式匹配失敗,因此執行else塊。

總之,嵌套模式消除了導航對象的意外複雜性,使我們能專註這些對象所表示的數據。它們還賦予我們集中處理錯誤的能力,因為如果一個值無法與嵌套模式P(Q)匹配,那子模式P和Q中的任何一個或兩個都無法匹配。我們不需要檢查和處理每個單獨的子模式匹配失敗——要麼整個模式匹配,要麼不匹配。

7 描述

使用可嵌套的記錄模式。

模式語法變為:

Pattern:
  TypePattern
  RecordPattern

TypePattern:
  LocalVariableDeclaration

RecordPattern:
  ReferenceType ( [ PatternList ] )

PatternList: 
  Pattern { , Pattern }

8 記錄模式

由記錄類類型和(可能為空的)模式列表組成,該列表用於與相應的記錄組件值進行匹配。

如聲明

record Point(int i, int j) {}

如果值v與記錄模式Point(int i, int j)匹配,則它是記錄類型Point的實例;如這樣,模式變數i將被初始化為在值v上調用與i對應的訪問器方法的結果,模式變數j將被初始化為在值v上調用與j對應的訪問器方法的結果。(模式變數的名稱不需要與記錄組件的名稱相同;也就是說,記錄模式Point(int x, int y)的行為相同,只是模式變數x和y被初始化。)

null值不與任何記錄模式匹配。

記錄模式可用var來匹配記錄組件,而無需聲明組件的類型。在這種情況下,編譯器會推斷由var模式引入的模式變數的類型。如模式Point(var a, var b)是模式Point(int a, int b)的簡寫。

記錄模式聲明的模式變數集合包括模式列表中聲明的所有模式變數。

如果一個表達式可以在不需要未經檢查的轉換的情況下將其轉換為模式中的記錄類型,則該表達式與記錄模式相容。

如果記錄模式命名了一個泛型記錄類,但沒有給出類型參數(即,記錄模式使用原始類型),則始終會推斷類型參數。例如:

// Java 21及以後
record MyPair<S,T>(S fst, T snd){};
static void recordInference(MyPair<String, Integer> pair){
    switch (pair) {
        case MyPair(var f, var s) -> 
            ... // 推斷的記錄模式 MyPair<String,Integer>(var f, var s)
        ...
    }
}

記錄模式的類型參數推斷在支持記錄模式的所有結構中都受到支持,即instanceof表達式和switch語句和表達式。

推斷適用於嵌套記錄模式;例如:

// Java 21及以後
record Box<T>(T t) {}
static void test1(Box<Box<String>> bbs) {
    if (bbs instanceof Box<Box<String>>(Box(var s))) {
        System.out.println("String " + s);
    }
}

這裡,嵌套模式Box(var s)的類型參數被推斷為String,因此模式本身被推斷為Box(var s)。

甚至可省略外部記錄模式中的類型參數,得到簡潔代碼:

// Java 21及以後
static void test2(Box<Box<String>> bbs) {
    if (bbs instanceof Box(Box(var s))) {
        System.out.println("String " + s);
    }
}

這裡編譯器會推斷整個instanceof模式為Box<Box<String>>(Box<String>(var s))

為保持相容性,類型模式不支持隱式推斷類型參數;如類型模式List l始終被視為原始類型模式。

9 記錄模式和完整的switch

JEP 441增強了switch表達式和switch語句,以支持模式標簽。無論是switch表達式還是模式switch語句,都必須是完整的:switch塊必須有處理選擇器表達式的所有可能值的子句。對於模式標簽,這是通過分析模式的類型來確定的;例如,case標簽case Bar b匹配類型為Bar及其所有可能的子類型的值。

對於涉及記錄模式的模式標簽,分析更加複雜,因為我們必須考慮組件模式的類型,並對密封層次結構進行調整。例如,考慮以下聲明:

class A {}
class B extends A {}
sealed interface I permits C, D {}
final class C implements I {}
final class D implements I {}
record Pair<T>(T x, T y) {}
Pair<A> p1;
Pair<I> p2;

以下switch不是完整的,因為沒有匹配包含兩個類型為A的值的對:

// Java 21及以後
switch (p1) {                 // 錯誤!
    case Pair<A>(A a, B b) -> ...
    case Pair<A>(B b, A a) -> ...
}

這兩個switch是完整的,因為介面I是密封的,因此類型C和D涵蓋了所有可能的實例:

// Java 21及以後
switch (p2) {
    case Pair<I>(I i, C c) -> ...
    case Pair<I>(I i, D d) -> ...
}

switch (p2) {
    case Pair<I>(C c, I i) -> ...
    case Pair<I>(D d, C c) -> ...
    case Pair<I>(D d1, D d2) -> ...
}

相比之下,這個switch不是完整的,因為沒有匹配包含兩個類型為D的值的對:

// Java 21及以後
switch (p2) {                        // 錯誤!
    case Pair<I>(C fst, D snd) -> ...
    case Pair<I>(D fst, C snd) -> ...
    case Pair<I>(I fst, C snd) -> ...
}

10 未來

記錄模式的描述中提到了許多可以擴展這裡描述的記錄模式的方向:

  • 可變參數模式,用於可變數量的記錄
  • 匿名模式,可以出現在記錄模式的模式列表中,匹配任何值,但不聲明模式變數
  • 適用於任意類的值而不僅僅是記錄類的模式。

我們可以在未來的JEP中考慮其中的一些方向。

11 依賴關係

本JEP建立在Pattern Matching for instanceof(JEP 394)的基礎上,該功能已在JDK 16中發佈。它與Pattern Matching for switch(JEP 441)共同演進。

本文由博客一文多發平臺 OpenWrite 發佈!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、引言 本文是京東到家自動化測試體系建設過程中的一些回顧和總結,刪減了部分系統設計與實踐的章節,保留了組織與文化相關的內容,整理成文,以饗讀者。 下麵就以QA(Quality Assurance)的視角來探討工作中經常面臨的問題與挑戰。 關於軟體質量,不知道你有沒有以下困惑: 西醫中“頭疼醫頭,腳 ...
  • 題目:員工工資單計算器 描述: 請編寫一個Python程式,該程式將通過用戶輸入來計算並列印員工的工資單。工資單應該包括員工的姓名、工作時長、每小時工資、毛工資、扣除額和凈工資。扣除額包括稅款和養老金。 要求: 1. 輸入: 員工姓名(字元串) 工作時長(整數,單位:小時) 每小時工資(浮點數,單位 ...
  • 在JDK 21中,Sequenced Collections的引入帶來了新的介面和方法來簡化集合處理。此增強功能旨在解決訪問Java中各種集合類型的第一個和最後一個元素需要非統一且麻煩處理場景。 下麵一起通過本文來瞭解一下不同集合處理示例。 Sequenced Collections介面 Seque ...
  • extern extern 是 C++ 中的一個關鍵字,用於聲明一個變數或函數是在其他文件中定義的。它的作用是告訴編譯器在鏈接時在其他文件中尋找該變數或函數的定義。 在 C++ 中,如果一個變數或函數在多個文件中使用,那麼就需要在每個文件中都聲明一次該變數或函數。這時就可以使用 extern 關鍵字 ...
  • 前言 最近寫一個任務隊列,可以支持存入返回值為void的任意函數對象。需要定義一個Task模板,來存儲函數對象以及參數。大致的實現如下: class Task { public: template <typename Func, typename... Args> Task(Func&& f, Ar ...
  • Python中有三種數字類型: int(整數) float(浮點數) complex(複數) 當您將值分配給變數時,將創建數字類型的變數: 示例:獲取您自己的Python伺服器 x = 1 # int y = 2.8 # float z = 1j # complex 要驗證Python中任何對象的類 ...
  • 1. 常規函數 函數都擁有顯示的類型簽名,其本身也是一種類型。 1.1 函數類型 自由函數 // 自由函數 fn sum(a: i32, b: i32) -> i32 { a+b } fn main() { assert_eq!(3, sum(1, 2)) } 關聯函數與方法 struct A(i3 ...
  • 近幾年來Laravel在PHP領域大放異彩,逐漸成為PHP開發框架中的中流砥柱。 這個系列的文章, 會帶你一起探知Laravel框架底層的實現細節。與其他框架相比,Laravel的設計理念確實更為先進(服務、容器、依賴註入、facade。。。),初讀代碼時會感覺代碼晦澀難懂,而一旦弄清了整套框架的基 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...