就在3月19日,Java22重磅發布。Java22新增了12項增強功能,其中包括七個預覽特性和一個孵化器特性,這些功能都顯著到足以引起JDK增強提案(JEPs)的關注。它們涵蓋了Java語言、其API、性能以及JDK中包含的工具的改進。
真的卷不動了,,前段時間才將項目升級到Java17...
接下來我們看看具體的新特性介紹...
匿名變量和模式。當需要但未使用變量聲明或嵌套模式時,提高了可讀性。這兩者都用下劃線字符表示。
優化:
比如我們可以在循環中這樣使用:
static int count(Iterable<Order> orders) { int total = 0; for (Order _ : orders) // Unnamed variable total++; return total;}
或者:
for (int i = 0, _ = sideEffect(); i < 10; i++) { ... i ... }
或者while
循環:
while (q.size() >= 3) { var x = q.remove(); var _ = q.remove(); // Unnamed variable var _ = q.remove(); // Unnamed variable ... new Point(x, 0) ...}
String s = ...try { int i = Integer.parseInt(s); ... i ...} catch (NumberFormatException _) { // Unnamed variable System.out.println("Bad number: " + s);}
多個catch
:
try { ... }catch (Exception _) { ... } // Unnamed variablecatch (Throwable _) { ... } // Unnamed variable
或者這樣使用try...resource
try (var _ = ScopedContext.acquire()) { // Unnamed variable ... no use of acquired resource ...}
在lamba中我們可以這樣使用:
...stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")) // Unnamed variable
例如:
switch (ball) { case RedBall _ -> process(ball); // Unnamed pattern variable case BlueBall _ -> process(ball); // Unnamed pattern variable case GreenBall _ -> stopProcessing(); // Unnamed pattern variable}
或者:
switch (box) { case Box(RedBall _) -> processBox(box); // Unnamed pattern variable case Box(BlueBall _) -> processBox(box); // Unnamed pattern variable case Box(GreenBall _) -> stopProcessing(); // Unnamed pattern variable case Box(var _) -> pickAnotherBox(); // Unnamed pattern variable}
通過這種改進允許我們省略名稱,未命名的模式變量使得基于類型模式的運行時數據探索在switch語句塊以及使用instanceof運算符時,視覺上更加清晰明了。
構造器中的前置語句。在構造函數中,允許在顯式構造函數調用之前出現不引用正在創建的實例的語句。
優化:
有時我們需要驗證傳遞給超類構造函數的參數。雖然我們可以在事后進行參數驗證,但這意味著可能會進行不必要的操作。例如如下:
public class PositiveBigInteger extends BigInteger { public PositiveBigInteger(long value) { super(value); // Potentially unnecessary work if (value <= 0) throw new IllegalArgumentException("non-positive value"); }}
Java22中的做法是聲明一個能夠快速失敗的構造函數,即在調用超類構造函數之前先驗證其參數。目前我們只能采用內聯方式實現這一點,即借助于輔助靜態方法:
public class PositiveBigInteger extends BigInteger { public PositiveBigInteger(long value) { super(verifyPositive(value)); } private static long verifyPositive(long value) { if (value <= 0) throw new IllegalArgumentException("non-positive value"); return value; }}
我們還可以將驗證邏輯直接包含在構造函數內部,這段代碼將會更具可讀性。
public class PositiveBigInteger extends BigInteger { public PositiveBigInteger(long value) { if (value <= 0) throw new IllegalArgumentException("non-positive value"); super(value); }}
為了給超類構造函數提供參數,我們必須執行另外的計算,再次不得不借助于輔助方法:
public class Sub extends Super { public Sub(Certificate certificate) { super(prepareByteArray(certificate)); } // 輔助方法 private static byte[] prepareByteArray(Certificate certificate) { var publicKey = certificate.getPublicKey(); if (publicKey == null) throw new IllegalArgumentException("null certificate"); return switch (publicKey) { case RSAKey rsaKey -> ... case DSAPublicKey dsaKey -> ... ... default -> ... }; }}
超類構造函數接受一個字節數組作為參數,而子類構造函數接受一個Certificate
對象作為參數。為了滿足超類構造函數調用必須為子類構造函數中的第一條語句這一限制,我們聲明了一個輔助方法prepareByteArray
來為此調用準備參數。
如果能夠將參數準備代碼直接嵌入到構造函數中,這段代碼會更具可讀性。在Java22中我們可以這么做:
public Sub(Certificate certificate) { var publicKey = certificate.getPublicKey(); if (publicKey == null) throw new IllegalArgumentException("null證書"); final byte[] byteArray = switch (publicKey) { case RSAKey rsaKey -> ... // RSA密鑰轉換為字節數組 case DSAPublicKey dsaKey -> ... // DSA公鑰轉換為字節數組 ... default -> ... // 其他情況處理邏輯 }; super(byteArray); }
字符串模板。字符串模板通過將文本文字與嵌入表達式和模板處理器相結合,以產生專門的結果,來補充 Java 的現有字符串文字和文本塊。
優化:
字符串的模板可以直接在代碼中表達,就像注釋字符串一樣,Java 運行時會自動將特定于模板的規則應用于字符串。從模板編寫字符串將使開發人員不必費力地轉義每個嵌入表達式、調用validate()
整個字符串或使用java.util.ResourceBundle
來查找本地化字符串。
比如我們可以構造一個表示JSON文檔的字符串,然后將其提供給JSON解析器:
String name = "Joan Smith";String phone = "555-123-4567";String address = "1 Maple Drive, Anytown";String json = """ { "name": "%s", "phone": "%s", "address": "%s" } """.formatted(name, phone, address);JSONObject doc = JSON.parse(json);
字符串的 JSON 結構可以直接在代碼中表達,Java運行時會JSONObject
自動將字符串轉換為。無需通過解析器進行手動繞行。
我們使用基于模板的字符串組合機制,我們就可以提高幾乎每個Jav 程序的可讀性和可靠性。這種功能將提供插值的好處,就像在其他編程語言中看到的那樣,但不太容易引入安全漏洞。它還可以減少使用將復雜輸入作為字符串的庫的繁瑣。
我們還可以使用模板STR
處理器,STR
是 Java 平臺中定義的模板處理器。它通過將模板中的每個嵌入表達式替換為該表達式的(字符串化)值來執行字符串插值。STR
是public
static
final
自動導入到每個Java源文件中的字段。
// Embedded expressions can be stringsString firstName = "Bill";String lastName = "Duck";String fullName = STR."/{firstName} /{lastName}";| "Bill Duck"String sortName = STR."/{lastName}, /{firstName}";| "Duck, Bill"http:// Embedded expressions can perform arithmeticint x = 10, y = 20;String s = STR."/{x} + /{y} = /{x + y}";| "10 + 20 = 30"http:// Embedded expressions can invoke methods and access fieldsString s = STR."You have a /{getOfferType()} waiting for you!";| "You have a gift waiting for you!"String t = STR."Access at /{req.date} /{req.time} from /{req.ipAddress}";| "Access at 2022-03-25 15:34 from 8.8.8.8"
模板表達式的模板可以跨越多行源代碼,使用類似于文本塊的語法。
String title = "My Web Page";String text = "Hello, world";String html = STR.""" <html> <head> <title>/{title}</title> </head> <body> <p>/{text}</p> </body> </html> """;| """| <html>| <head>| <title>My Web Page</title>| </head>| <body>| <p>Hello, world</p>| </body>| </html>| """String name = "Joan Smith";String phone = "555-123-4567";String address = "1 Maple Drive, Anytown";String json = STR.""" { "name": "/{name}", "phone": "/{phone}", "address": "/{address}" } """;| """| {| "name": "Joan Smith",| "phone": "555-123-4567",| "address": "1 Maple Drive, Anytown"| }| """
隱式聲明的類和實例主方法。這項Java增強引入了隱式聲明的類以及實例主方法的功能,允許開發人員在不明確顯式聲明類的情況下編寫類結構,并能夠在類實例上直接定義和執行類似于傳統main
方法的入口點。這一特性旨在簡化編程模型,特別是對于初學者和小型腳本場景,使得無需了解大型程序設計所需的完整類聲明結構也能快速編寫可運行的Java代碼。
優化:
總體來說可以快速學習Java。
我們以入門Java的第一行代碼Hello World
為例:
class HelloWorld { void main() { System.out.println("Hello, World!"); }}
Java22還可以隱式聲明一個類:
void main() { System.out.println("Hello, World!");}
還可以這樣:
String greeting() { return "Hello, World!"; }void main() { System.out.println(greeting());}
或者,使用字段,如:
String greeting = "Hello, World!";void main() { System.out.println(greeting);}
外部函數和內存API。允許Java程序與Java運行時之外的代碼和數據進行交互。通過高效地調用外部函數(即JVM外部的代碼)和安全地訪問外部內存(即JVM不管理的內存),該API使Java程序能夠調用本地庫并處理本地數據,而無需JNI的脆弱性和危險性。
優化
Java22提供外部函數和內存API(FFM API)定義類和接口,以便開發者使用他們可以
MemorySegment
、Arena
和 SegmentAllocator
)的分配和釋放,MemoryLayout
和VarHandle
Linker
、SymbolLookup
、FunctionDescriptor
和 MethodHandle
)。FFM API在java.lang.foreign
包中。
類文件API。提供了用于解析、生成和轉換 Java 類文件的標準 API。
優化:
Java22為 Class-File API 采用了以下設計目標和原則。
Class-File API
在 java.lang.classfile
包和子包中。 它定義了三個主要抽象:
流收集器。增強了 Stream API,以支持自定義中間操作。這將允許流管道以不易通過現有內置中間操作實現的方式轉換數據。
優化:
流(Stream)::gather(Gatherer) 是一種新的中間流操作,通過應用用戶自定義實體——收集器(Gatherer)來處理流中的元素。利用gather操作,我們可以構建高效且適用于并行處理的流,實現幾乎所有的中間操作。Stream::gather(Gatherer) 在中間操作中的作用類似于Stream::collect(Collector)在終止操作中的作用。
Gatherer用于表示對流中元素的轉換,它是java.util.stream.Gatherer接口的一個實例。Gatherer可以以一對一、一對多、多對一或多對多的方式轉換元素。它可以跟蹤已處理過的元素以影響后續元素的轉換,支持短路操作以將無限流轉換為有限流,并能啟用并行執行。例如,一個Gatherer可以從輸入流中按條件轉換一個輸入元素為一個輸出元素,直到某一條件變為真,此時開始將一個輸入元素轉換為兩個輸出元素。
Gatherer由四個協同工作的函數定義:
當調用Stream::gather時,執行以下等效步驟:
現有Stream接口中聲明的所有中間操作都可以通過調用帶有實現該操作的Gatherer的gather方法來實現。例如,對于一個T類型元素的流,Stream::map通過應用一個函數將每個T元素轉換為U元素并將其向下傳遞;這實質上就是一個無狀態的一對一Gatherer。另一個例子是Stream::filter,它采用一個謂詞決定輸入元素是否應向下傳遞;這只是一個無狀態的一對多Gatherer。事實上,從概念上講,每一個流管道都可以等同于:
source.gather(...).gather(...).gather(...).collect(...)
結構化并發。簡化了并發編程。結構化并發將在不同線程中運行的相關任務組視為單個工作單元,從而簡化了錯誤處理和取消,提高了可靠性并增強了可觀察性。
優化:
結構化并發API的主要類是java.util.concurrent
包中的StructuredTaskScope
類。此類允許開發人員將任務結構化為一組并發子任務,并將它們作為一個整體進行協調管理。子任務通過分別創建新線程(fork)并在之后作為一個整體等待它們完成(join)和可能的情況下作為一個整體取消它們。子任務的成功結果或異常將被父任務聚合并處理。StructuredTaskScope
確保了子任務的生命周期被限定在一個清晰的詞法作用域內,在這個作用域內,任務與其子任務的所有交互,包括分叉(fork
)、同步(join
)、取消(cancellation
)、錯誤處理和結果合成都在此發生。
使用StructuredTaskScopes實例:
Response handle() throws ExecutionException, InterruptedException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier<String> user = scope.fork(() -> findUser()); Supplier<Integer> order = scope.fork(() -> fetchOrder()); scope.join() // 同步兩個子任務 .throwIfFailed(); // 并傳播錯誤信息 // 這里,兩個子任務都已經成功,所以組合它們的結果 return new Response(user.get(), order.get()); }}
這里理解涉及線程的生命周期變得簡單:在所有情況下,它們的生命周期都被限制在一個詞法作用域內,即try-with-resources
語句的主體內。此外,使用StructuredTaskScope
確保了一系列有價值的特性:
findUser()
或fetchOrder()
子任務之一失敗,尚未完成的另一個子任務將被取消。(這是由ShutdownOnFailure
實現的關閉策略管理的,也有可能實現其他策略)。join()
之前或期間執行handle()
方法的線程被中斷,則當線程退出作用域時,兩個子任務都將自動取消。findUser()
和fetchOrder()
的線程在轉儲中顯示為scope的子線程。作用域值優化。在線程內和跨線程之間有效共享不可變數據。這個Java增強它旨在提供一種機制,允許開發者在Java應用程序中安全地在線程內部以及跨線程之間共享不可變數據。該特性旨在替代或改善現有的ThreadLocal機制,提供一種更加可控、易于管理和高效的解決方案,尤其是在涉及大規模并發處理和跨層數據傳遞場景時。通過范圍值,開發人員可以更好地組織和管理在特定作用域內有效的變量,同時確保資源的有效利用和數據的安全共享。
優化:
作用域值是一種容器對象,允許方法在同一個線程內安全高效地將其數據值與直接和間接的被調用者共享,同時也能與子線程共享,而無需依賴方法參數。它是一個類型為ScopedValue
的變量,通常被聲明為final static
字段,并設置為private
訪問權限,以防止其他類的代碼直接訪問。
類似線程局部變量,作用域值關聯了多個值,每個線程對應一個。具體使用的值取決于哪個線程調用了它的方法。不同于線程局部變量,范圍限定值只被寫入一次,并且在線程執行期間只能在一定時間段內可用。
作用域值的使用如下所示。一些代碼調用ScopedValue.where
,提供一個范圍限定值及其要綁定的對象。調用run方法會綁定該范圍限定值,為當前線程提供一個特定的副本,然后執行作為參數傳遞的lambda
表達式。在run方法執行期間,lambda
表達式或從中直接或間接調用的任何方法,都可以通過值的get()
方法讀取范圍限定值。當run方法執行完畢后,該綁定關系會被銷毀。
final static ScopedValue<...> NAME = ScopedValue.newInstance();// 在某個方法中ScopedValue.where(NAME, <value>) .run(() -> { ... NAME.get() ... 調用方法 ... });// 在lambda表達式中直接或間接調用的方法中... NAME.get() ...
代碼的結構明確了線程可以讀取其作用域值副本的時間段。這個有限的生命周期極大地簡化了對線程行為的推理。數據從調用者單向傳輸至直接和間接的被調用者,一眼就能看出。不存在能讓遠端代碼隨時改變范圍限定值的set方法。這也有助于提高性能:無論調用者和被調用者的棧距離如何,通過get()方法讀取作用域值的速度常常與讀取局部變量一樣快。
矢量API。一個能夠在支持的CPU架構上運行時可靠編譯為最優矢量指令的API,從而實現優于等效標量計算的性能。
本JEP提議在JDK 22中重新孵化該API,相比于JDK 21版本,API進行了些許增強。實現內容包括bug修復和性能優化。主要變更如下:
支持通過任意原始元素類型的數組支持的堆內存MemorySegments
進行矢量訪問。在此之前,訪問僅限于由字節數組支持的堆內存MemorySegments
。
優化:
Project Valhalla
項目的契合Vector API
的長期目標是利用Project Valhalla
對Java對象模型的增強功能。主要來說,這意味著將Vector API
當前基于值的類更改為值類,以便程序能夠處理值對象,即缺乏對象標識性的類實例。因此,Vector API
將在多個版本中孵化,直至Project Valhalla
的必要特性作為預覽功能可用。一旦這些Valhalla特性可用,我們將調整Vector API及其實現以使用這些特性,并將Vector API
本身提升為預覽功能。向量由抽象類Vector<E>
表示。類型變量E被實例化為矢量覆蓋的標量基本整數或浮點元素類型的裝箱類型。一個向量還具有形狀屬性,該屬性定義了矢量的大?。ㄒ晕粸閱挝唬?。當矢量計算由HotSpot C2
編譯器編譯時,向量的形狀決定了Vector<E>
實例如何映射到硬件矢量寄存器。向量的長度,即車道數或元素個數,等于矢量大小除以元素大小。
支持的一系列元素類型(E)包括Byte、Short、Integer、Long、Float和Double,分別對應于標量基本類型byte、short、int、long、float和double。
支持的一系列形狀對應于64位、128位、256位和512位的矢量大小,以及最大位數。512位形狀可以將字節打包成64個車道,或者將整數打包成16個車道,具有這種形狀的矢量可以一次性操作64個字節或16個整數。max-bits形狀支持當前架構的最大矢量尺寸,這使得API能夠支持ARM SVE
平臺,該平臺實現可以支持從128位到2048位,以128位為增量的任何固定尺寸。
區域固定。通過在G1中實現區域固定(regional pinning),從而在Java Native Interface (JNI) 臨界區域內不需要禁用垃圾收集,以此來減少延遲。
優化:
啟動多文件源代碼程序。允許用戶在不首先編譯程序的情況下運行由多個 Java 源代碼文件提供的程序。
優化:
Java22增強了Java啟動器的源文件模式,使其能夠運行以多份Java源代碼文件形式提供的程序。
舉例來說,假設一個目錄包含了兩個文件:Prog.java和Helper.java,每個文件各聲明一個類:
// Prog.javaclass Prog { public static void main(String[] args) { Helper.run(); }}// Helper.javaclass Helper { static void run() { System.out.println("Hello!"); }}
運行命令java Prog.java
將會在內存中編譯Prog類并調用其main方法。由于Prog類中的代碼引用了Helper類,啟動器會在文件系統中查找Helper.java文件,并在內存中編譯Helper類。如果Helper類中的代碼又引用了其他類,例如HelperAux類,那么啟動器還會找到HelperAux.java并對其進行編譯。
當不同.java文件中的類互相引用時,Java啟動器并不保證按照特定順序或時間點編譯這些.java文件。例如,啟動器可能先編譯Helper.java再編譯Prog.java。有些代碼可能在程序開始執行前就已經被編譯,而其他代碼可能在需要時才懶加載編譯。
只有被程序引用到的類所在的.java文件才會被編譯。這樣一來,開發者可以在嘗試新版本代碼時不必擔心舊版本會被意外編譯。例如,假設目錄中還包含OldProg.java文件,其中包含Progr類的一個舊版本,該版本期望Helper類有一個名為go的方法而不是run方法。當運行Prog.java時,存在包含潛在錯誤的OldProg.java文件并不會影響程序執行。
一個.java文件中可以聲明多個類,且會被一起編譯。在一個.java文件中共聲明的類優先于在其他.java文件中聲明的類。例如,假設上面的Prog.java文件擴展后也在其中聲明了Helper類,盡管Helper.java文件中已有一個同名類。當Prog.java中的代碼引用Helper時,會使用在Prog.java中共同聲明的那個類;啟動器不會去搜索Helper.java文件。
源代碼程序中禁止重復的類聲明。也就是說,同一個.java文件內或構成程序的不同.java文件之間的類聲明,如果名稱相同,則不允許存在。假設經過編輯后,Prog.java和Helper.java最終變成以下形式,其中類Aux意外地在兩個文件中都被聲明:
// Prog.javaclass Prog { public static void main(String[] args) { Helper.run(); Aux.cleanup(); }}class Aux { static void cleanup() { ... }}// Helper.javaclass Helper { static void run() { ... }}class Aux { static void cleanup() { ... }}
運行命令java Prog.java
會編譯Prog.java中的Prog和Aux類,調用Prog類的main方法,然后——由于main方法引用了Helper——查找并編譯Helper.java中的Helper和Aux類。Helper.java中對Aux類的重復聲明是不允許的,所以程序會停止運行,啟動器報告錯誤。
當通過Java啟動器傳遞單個.java文件名稱時,就會觸發其源文件模式。如果提供了額外的文件名,它們會成為主方法的參數。例如,運行命令java Prog.java Helper.java
會導致字符串數組"Helper.java"作為參數傳給Prog類的main方法。
除了JEP中描述的更改之外,發行說明中還列出了許多較小的更新,這些更新對許多應用程序開發者有重要意義。其中包括廢棄過時的API和移除先前已經棄用的API。
最后,JDK 22是通過六個月的發布節奏按時交付的13th功能版本。由于預期改進源源不斷,這種程度的可預測性使開發人員能夠輕松管理創新的采用。Oracle 不會為 JDK 22 提供長期支持,在 2023 年 9 月之前提供更新,之后它將被 Oracle JDK 23 取代。最近的長期維護版本是Java 21。
本文鏈接:http://www.www897cc.com/showinfo-26-78502-0.htmlJava22重磅發布!??!卷不動了,真的卷不動了......
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 面試官:說說零拷貝的實現原理?
下一篇: 15個CSS 常見錯誤,請一定要注意避免