Java21新特性的實踐,確實很絲滑!

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:

👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號等等功能:
  • Boot 地址:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 地址:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn


JDK 21 於 2023 年 9 月 19 日釋出,是繼之前的 LTS 版本 JDK 17 之後最新的長期支援 (LTS) 版本。在本文中,我們將探討 JDK 21 新引入的功能。

以下是 JDK 21 的新功能列表:
  • 虛擬執行緒
  • 序列集合
  • 記錄模式
  • 字串模板(預覽)
  • 未命名模式和變數(預覽)
  • 未命名類和例項主要方法(預覽)
  • 作用域值(預覽)
  • 結構化併發(預覽)

1 虛擬執行緒

從 Java 程式碼的角度來看,虛擬執行緒感覺就像普通執行緒,但它們沒有 1:1 對映到作業系統/平臺執行緒。它是從虛擬執行緒到載體執行緒進而到作業系統執行緒的M:N對映。
有一個所謂的載體執行緒池,虛擬執行緒臨時對映(“安裝”)到該執行緒池上。一旦虛擬執行緒遇到阻塞操作,虛擬執行緒就會從載體執行緒中移除(“解除安裝”),並且載體執行緒可以執行另一個虛擬執行緒(新的或之前被阻塞的虛擬執行緒)。
載體執行緒池是ForkJoinPool

虛擬執行緒的一些優點:
  • 提高應用程式吞吐量
  • 提高應用程式可用性
  • 減少記憶體消耗

建立虛擬執行緒

要建立虛擬執行緒,我們可以使用 Thread.ofVirtual() 工廠方法並傳遞可執行物件。
  1. Thread.ofVirtual().start(Runnable);
  2. Thread.ofVirtual().unstarted(Runnable);
如果你想讓虛擬執行緒立即啟動,你可以使用start() 方法,它會立即執行傳遞給它的Runnable start()
如果不希望虛擬執行緒立即啟動,可以使用該unstarted()方法。

建立使用虛擬執行緒的ExecutorService

我們只需要替換newFixedThreadPoolnewVirtualThreadPerTaskExecutor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

publicclassVirtualThreadExample{

publicstaticvoidmain(String[] args){
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        executor.submit(() -> {
            System.out.println(Thread.currentThread().getName())
        });

        executor.shutdown();
    }
}

基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

2 順序集合

順序集合為我們提供了defined encounter order(是一種所見即所得的順序,含義是從佇列中取出元素的順序既是你存放該元素時候的順序),用於訪問第一個和最後一個元素並以相反的順序迭代。
這意味著我們可以在集合的兩端新增、檢索或刪除元素。

publicinterfaceSequencedCollection<EextendsCollection<E{
defaultvoidaddFirst(E e){ ... }
defaultvoidaddLast(E e){ ... }
default E getFirst(){ ... }
default E getLast(){ ... }
default E removeFirst(){ ... }
default E removeLast(){ ... }
SequencedCollection<E> reversed();
}
正如我們所看到的,除了reverse()之外的所有方法都是預設方法並提供預設實現。
這意味著現有的集合類(例如 ArrayList 和 LinkedList)都可以實現此介面,而無需更改其程式碼。
ArrayList<Integer> list = new ArrayList<>();
        list.add(1); // [1]
        list.addFirst(0); // [0, 1]
        list.addLast(2); // [0, 1, 2]
        list.getFirst(); // 0
        list.getLast(); // 2
        list.reversed(); // [2, 1, 0]

SequencedSet

SequencedSet 介面對於具有有序元素的 Set 非常有用,特別是當您必須執行某些操作(例如檢索或刪除第一個或最後一個索引處的元素)時。它還提供了一種反轉元素的方法。
您還需要知道兩個 SequencedSet 物件的比較與其他型別的 Set 相同,不依賴於元素順序。
interfaceSequencedSet<EextendsSet<E>, SequencedCollection<E{
SequencedSet<E> reversed();
}
LinkedHashSet 是 Set 的一個實現,它實現了 SequencedSet 介面。
因此,您可以使用 LinkedHashSet 來建立 SequencedSet。
Set 的其他實現(例如 HashSet 和 TreeSet)未實現該介面。
讓我們探索一些示例來演示如何訪問第一個和最後一個元素,以及如何使用反向函式:
SequencedSet<String> values = new LinkedHashSet<>();
        values.add("one");
        values.add("two");
        System.out.println(values); // [one, two]

        values.addFirst("zero");
        System.out.println(values); // [zero, one, two]
        values.addFirst("one");
        System.out.println(values); // [one, zero, two]

        values.addLast("three");
        System.out.println(values); // [one, zero, two, three]

        values.removeFirst();
        System.out.println(values); // [zero, two, three]

        SequencedSet<String> reversedSet = values.reversed();
        System.out.println(reversedSet); // [three, two, zero]

boolean isEqual = values.equals(reversedSet);
        System.out.println(isEqual); // true
        System.out.println(values.hashCode()); // 612888
        System.out.println(reversedSet.hashCode()); // 612888
        System.out.println(values.hashCode() == reversedSet.hashCode()); // true

SequencedMap

如果要使用 SequencedMap 中定義的新方法,則需要使用 Map 實現,例如 LinkedHashMap 或實現 SortedMap 的 Map。
HashMap 不利用 Sequenced Collections,因為它沒有定義 defined encounter order(是一種所見即所得的順序,含義是從佇列中取出元素的順序既是你存放該元素時候的順序)。
interfaceSequencedMap<K,VextendsMap<K,V{
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
    SequencedSet<Entry<K,V>> sequencedEntrySet();
putFirst(K, V);
putLast(K, V);
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
在下面的示例中,正如您所看到的,我們可以透過firstEntry()lastEntry()方法訪問第一個和最後一個元素。
pollFirstEntry()方法將刪除並返回第一個鍵值元素,如果對映為空,則返回 null。
此外,呼叫reverse()只會比較元素,而不依賴於它們的順序。
SequencedMap<String, Integer> myMap = new LinkedHashMap<>();
        myMap.put("one"1);
        myMap.put("two"2);
        System.out.println(myMap); // {one=1, two=2}

        Entry<String, Integer> firstEntry = myMap.firstEntry();
        System.out.println(firstEntry); // one=1

        Entry<String, Integer> lastEntry = myMap.lastEntry();
        System.out.println(lastEntry); // two=2

        myMap.putFirst("zero"0);
        System.out.println(myMap); // {zero=0, one=1, two=2}
        myMap.putFirst("one", -1);
        System.out.println(myMap); // {one=-1, zero=0, two=2}

        Entry<String, Integer> polledFirstEntry = myMap.pollFirstEntry();
        System.out.println(polledFirstEntry); // one=-1
        System.out.println(myMap); // {zero=0, two=2}

        SequencedMap<String, Integer> reversedMap = myMap.reversed();
        System.out.println(reversedMap); // {two=2, zero=0}

boolean isEqual = myMap.equals(reversedMap);
        System.out.println(isEqual); // true
        System.out.println(myMap.hashCode()); // 692224
        System.out.println(reversedMap.hashCode()); // 692224
        System.out.println(myMap.hashCode() == reversedMap.hashCode()); // true

基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

3 字串模板

這是預覽功能,預設停用,我們需要使用
--enable-preview啟用字串模板。
首先,在深入探討字串模板之前,我將探討一些用於組合字串的技術。
+(加號)運算子: 最大的缺點是每次使用 + 運算子時都會建立一個新字串。
StringBuffer 和 StringBuilder: StringBuffer 是執行緒安全的,而 StringBuilder 是在 Java 5 中新增的,效能更高,但不是執行緒安全的替代方案。
它們的主要缺點是冗長,尤其是對於更簡單的字串:
var greeting = new StringBuilder()
        .append("Hello, welcome ")
        .append(name)
        .toString();
String::format 和 String::formatter: 它們允許可重用模板,但它們要求我們指定格式並以正確的順序提供變數。
var format = "Good morning %s, It's a beautiful day!";
var text = String.format(format, name);
// Java 15+
var text = format.formatter(name);
儘管我們節省了字串分配的數量,但現在 JVM 必須解析/驗證模板字串。
java.text.MessageFormat: 與String格式相同,但更詳細
var format = new MessageFormat("Good morning {0}, It's a beautiful day!");
var greeting = format.format(name);

現在我們有字串模板來拯救

它簡單、簡潔,處理字串的新方法稱為模板表示式。它們可以執行插值,還為我們提供了組合字串的靈活性,並將結構化文字轉換為任何物件,而不僅僅是字串。
模板表示式由三個組成部分組成:
  • 模板處理器:Java 提供了兩種用於執行字串插值的模板處理器:STR 和 FMT
  • 包含包裝表示式的模板,如 {name}
  • 點 (.) 字元
以下是一些關於如何將字串模板與模板處理器一起使用的示例:
package com.mina.stringtemplates;

importstatic java.util.FormatProcessor.FMT;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

publicclassStringTemplateExamples{

publicstatic String greeting(String firstName, String lastName){
return STR."Hello! Good morning \{ firstName } \{ lastName }" ;
    }

publicstatic String multiplyWithArithmeticExpressions(int a, int b){
return STR."\{ a } times \{ b } = \{ a * b }" ;
    }

publicstatic String multiplyWithJavaExpression(int a, int b){
return STR."\{ a } times \{ b } = \{ Math.multiplyExact(a, b) }" ;
    }

//  multiplication with floating point numbers rounded to two decimal places using the FMT template processor
publicstatic String multiplyFloatingNumbers(double a, double b){
return FMT."%.2f\{ a } times %.2f\{ b } = %.2f\{ a * b }" ;
    }

publicstatic String getErrorResponse(int httpStatus, String errorMessage){
return STR."""
                {
                  "
httpStatus": \{ httpStatus },
                  "
errorMessage": "\{ errorMessage }"
                }"
"" ;
    }

publicstatic String getCurrentDate(){
return STR."Today's date: \{
        LocalDate.now().format(
                DateTimeFormatter.ofPattern("
yyyy-MM-dd")
        ) }"
 ;
}
}

4 記錄模式

記錄模式匹配是一種在單個步驟中匹配記錄型別並訪問其元件的方法。
我們用它來測試一個值是否是記錄類型別的例項,如果是,則對其元件值執行模式匹配。
下面的示例測試是否是具有記錄模式transaction的記錄例項TransactionTransaction(String type, double amount)
package com.mina.recordpattern;

publicclassRecordPatternExample{

// I'm using "_" for readability here, this won't compile
publicstatic String getTransactionType(Transaction transaction){
returnswitch (transaction) {
casenull -> thrownew IllegalArgumentException("Transaction can not be null.");
caseTransaction(String type, double amount) when type.equals("Deposit") && amount > 0 -> "Deposit";
caseTransaction(String type, _) when type.equals("Withdrawal") -> "Withdrawal";
default -> "Unknown transaction type";
        };
    }

record Transaction(String type, double amount){

    }
}

如果事務為空,會發生什麼?你是對的——丟擲了一個空指標異常。這也是Java 21中的情況,但是現在我們可以透過寫case null->來顯式地使用null case,這樣可以避免NullPointerException。
保護模式:也可以保護特定情況。例如,我們使用when關鍵字來檢查相等性。

5 switch 模式匹配

switch模式匹配在 Java 17 中作為預覽功能引入,並在 Java 21 中永久保留。
語句switch將控制轉移到多個語句或表示式之一,具體取決於其選擇器表示式的值(可以是任何型別),並且case標籤可以具有模式。
它檢查其選擇器表示式是否與模式匹配,與測試其選擇器表示式是否完全等於常量相比,這更具可讀性和靈活性。
package com.mina.switchpatternmatching;

import com.mina.switchpatternmatching.SwitchPatternMatchingExample.Transaction.Deposit;
import com.mina.switchpatternmatching.SwitchPatternMatchingExample.Transaction.Withdrawal;

publicclassSwitchPatternMatchingExample{

publicstatic String getTransactionType(Transaction transaction){
returnswitch (transaction) {
casenull:
thrownew IllegalArgumentException("Transaction can't be null.");
case Deposit deposit when deposit.getAmount() > 0//  Guarded pattern with when clause
                yield "Deposit";
case Withdrawal withdrawal:
                yield "Withdrawal";
default:
                yield "Unknown transaction type";
        };
    }

    sealed classTransactionpermitsDepositWithdrawal{

privatedouble amount;

publicTransaction(double amount){
this.amount = amount;
        }

publicdoublegetAmount(){
return amount;
        }

finalclassWithdrawalextendsTransaction{

publicWithdrawal(double amount){
super(amount);
            }
        }

finalclassDepositextendsTransaction{

publicDeposit(double amount){
super(amount);
            }
        }
    }
}

希望這篇文章可以幫助您更好地瞭解 Java 21 的新功能!

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢

星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。

文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章