」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > Java 功能:詳細了解新 LTS 版本中最重要的變化

Java 功能:詳細了解新 LTS 版本中最重要的變化

發佈於2024-07-29
瀏覽:542

Another LTS Java release is already here, bringing some exciting changes and improvements. Let’s analyze the most important Java 21 features, check out how they work in practice, and try to predict their significance for the future of this technology.

Since the Java platform adopted a six-month release cycle, we’ve moved past the perennial questions such as “Will Java die this year?” or “Is it worth migrating to the new version?”. Despite 28 years since its first release, Java continues to thrive and remains a popular choice as the primary programming language for many new projects.

Java 17 was a significant milestone, but Java 21 has now taken 17’s place as the next long-term support release (LTS). It’s essential for Java developers to stay informed about the changes and new features this version brings. Inspired by my colleague Darek, who detailed Java 17 features in his article, I’ve decided to discuss JDK 21 in a similar fashion.

JDK 21 comprises a total of 15 JEPs (JDK Enhancement Proposals). You can review the complete list on the official Java site. In this article, I’ll highlight several Java 21 JEPs that I believe are particularly noteworthy. Namely:

  1. String Templates
  2. Sequenced Collections
  3. Pattern Matching for switch and Record Patterns
  4. Virtual Threads

Without further delay, let’s delve into the code and explore these updates.

String Templates (Preview)

The Spring Templates feature is still in preview mode. To use it, you have to add –enable-preview flag to your compiler args. However, I’ve decided to mention it despite its preview status. Why? Because I get very irritated every time I have to write a log message or sql statement that contains many arguments or decipher which placeholder will be replaced with a given arg. And Spring Templates promise to help me (and you) with that.

As JEP documentation says, the purpose of Spring Templates is to “simplify the writing of Java programs by making it easy to express strings that include values computed at run time”.

Let’s check if it really is simpler.

The “old way” would be to use the formatted() method on a String object:

var msg = "Log message param1: %s, pram2: %s".formatted(p1, p2);

Now, with StringTemplate.Processor (STR), it looks like this:

var interpolated = STR."Log message param1: \{p1}, param2: \{p2}";

With a short text like the one above, the profit may not be that visible – but believe me, when it comes to big text blocks (jsons, sql statements), named parameters will help you a lot.

Sequenced collections

Java 21 introduced a new Java Collection Hierarchy. Look at the diagram below and compare it to what you probably have learned during your programming classes. You’ll notice that three new structures have been added (highlighted by the green color).

Java features: A detailed look at the most important changes in the new LTS release
Source: JEP 431

Sequenced collections introduce a new built-in Java API, enhancing operations on ordered datasets. This API allows not only convenient access to the first and last elements of a collection but also enables efficient traversal, insertion at specific positions, and retrieval of sub-sequences. These enhancements make operations that depend on the order of elements simpler and more intuitive, improving both performance and code readability when working with lists and similar data structures.

This is the full listing of the SequencedCollection interface:

public interface SequencedCollection extends Collection {
   SequencedCollection reversed();
   default void addFirst(E e) {
       throw new UnsupportedOperationException();
   }
   default void addLast(E e) {
       throw new UnsupportedOperationException();
   }
   default E getFirst() {
       return this.iterator().next();
   }
   default E getLast() {
       return this.reversed().iterator().next();
   }
   default E removeFirst() {
       var it = this.iterator();
       E e = it.next();
       it.remove();
       return e;
   }
   default E removeLast() {
       var it = this.reversed().iterator();
       E e = it.next();
       it.remove();
       return e;
   }
}

So, now, instead of:

var first = myList.stream().findFirst().get();
var anotherFirst = myList.get(0);
var last = myList.get(myList.size() - 1);

We can just write:

var first = sequencedCollection.getFirst();
var last = sequencedCollection.getLast();
var reversed = sequencedCollection.reversed();

A small change, but IMHO it’s such a convenient and usable feature.

Pattern Matching and Record Patterns

Because of the similarity of Pattern Matching for switch and Record Patterns, I will describe them together. Record patterns are a fresh feature – they have been introduced in Java 19 (as a preview). On the other hand, Pattern Matching for switch is kinda a continuation of the extended instanceof expression. It brings in new possible syntax for switch statements which lets you express complex data-oriented queries more easily.

Let’s forget about the basics of OOP for the sake of this example and deconstruct the employee object manually (employee is a POJO class).

Before Java 21, It looked like this:

if (employee instanceof Manager e) {
   System.out.printf("I’m dealing with manager of %s department%n", e.department);
} else if (employee instanceof Engineer e) {
   System.out.printf("I’m dealing with %s engineer.%n", e.speciality);
} else {
   throw new IllegalStateException("Unexpected value: "   employee);
}

What if we could get rid of the ugly instanceof? Well, now we can, thanks to the power of Pattern Matching from Java 21:

switch (employee) {
   case Manager m -> printf("Manager of %s department%n", m.department);
   case Engineer e -> printf("I%s engineer.%n", e.speciality);
   default -> throw new IllegalStateException("Unexpected value: "   employee);
}

While talking about the switch statement, we can also discuss the Record Patterns feature. When dealing with a Java Record, it allows us to do much more than with a standard Java class:

switch (shape) { // shape is a record
   case Rectangle(int a, int b) -> System.out.printf("Area of rectangle [%d, %d] is: %d.%n", a, b, shape.calculateArea());
   case Square(int a) -> System.out.printf("Area of square [%d] is: %d.%n", a, shape.calculateArea());
   default -> throw new IllegalStateException("Unexpected value: "   shape);
}

As the code shows, with that syntax, record fields are easily accessible. Moreover, we can put some additional logic to our case statements:

switch (shape) {
   case Rectangle(int a, int b) when a  System.out.printf("Incorrect values for rectangle [%d, %d].%n", a, b);
   case Square(int a) when a  System.out.printf("Incorrect values for square [%d].%n", a);
   default -> System.out.println("Created shape is correct.%n");
}

We can use similar syntax for the if statements. Also, in the example below, we can see that Record Patterns also work for nested records:

if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
                          ColoredPoint lr)) {
   //sth
}

Virtual Threads

The Virtual Threads feature is probably the hottest one among all Java 21 – or at least one the Java developers have waited the most for. As JEP documentation (linked in the previous sentence) says, one of the goals of the virtual threads was to “enable server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization”. However, does this mean we should migrate our entire code that uses java.lang.Thread?

First, let’s examine the problem with the approach that existed before Java 21 (in fact, pretty much since Java’s first release). We can approximate that one java.lang.Thread consumes (depending on OS and configuration) about 2 to 8 MB of memory. However, the important thing here is that one Java Thread is mapped 1:1 to a kernel thread. For simple web apps which use a “one thread per request” approach, we can easily calculate that either our machine will be “killed” when traffic increases (it won’t be able to handle the load) or we’ll be forced to purchase a device with more RAM, and our AWS bills will increase as a result.

Of course, virtual threads are not the only way to handle this problem. We have asynchronous programming (frameworks like WebFlux or native Java API like CompletableFuture). However, for some reason – maybe because of the “unfriendly API” or high entry threshold – these solutions aren’t that popular.

Virtual Threads aren’t overseen or scheduled by the operating system. Rather, their scheduling is handled by the JVM. While real tasks must be executed in a platform thread, the JVM employs so-called carrier threads — essentially platform threads — to “carry” any virtual thread when it is due for execution. Virtual Threads are designed to be lightweight and use much less memory than standard platform threads.

The diagram below shows how Virtual Threads are connected to platform and OS threads:

Java features: A detailed look at the most important changes in the new LTS release

So, to see how Virtual Threads are used by Platform Threads, let’s run code that starts (1 number of CPUs the machine has, in my case 8 cores) virtual threads.

var numberOfCores = 8; //
final ThreadFactory factory = Thread.ofVirtual().name("vt-", 0).factory();
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
   IntStream.range(0, numberOfCores   1)
           .forEach(i -> executor.submit(() -> {
               var thread = Thread.currentThread();
               System.out.println(STR."[\{thread}]  VT number: \{i}");
               try {
                   sleep(Duration.ofSeconds(1L));
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }));
}

Output looks like this:

[VirtualThread[#29,vt-6]/runnable@ForkJoinPool-1-worker-7]  VT number: 6
[VirtualThread[#26,vt-4]/runnable@ForkJoinPool-1-worker-5]  VT number: 4
[VirtualThread[#30,vt-7]/runnable@ForkJoinPool-1-worker-8]  VT number: 7
[VirtualThread[#24,vt-2]/runnable@ForkJoinPool-1-worker-3]  VT number: 2
[VirtualThread[#23,vt-1]/runnable@ForkJoinPool-1-worker-2]  VT number: 1
[VirtualThread[#27,vt-5]/runnable@ForkJoinPool-1-worker-6]  VT number: 5
[VirtualThread[#31,vt-8]/runnable@ForkJoinPool-1-worker-6]  VT number: 8
[VirtualThread[#25,vt-3]/runnable@ForkJoinPool-1-worker-4]  VT number: 3
[VirtualThread[#21,vt-0]/runnable@ForkJoinPool-1-worker-1]  VT number: 0

So, ForkJonPool-1-worker-X Platform Threads are our carrier threads that manage our virtual threads. We observe that Virtual Threads number 5 and 8 are using the same carrier thread number 6.

The last thing about Virtual Threads I want to show you is how they can help you with the blocking I/O operations.

Whenever a Virtual Thread encounters a blocking operation, such as I/O tasks, the JVM efficiently detaches it from the underlying physical thread (the carrier thread). This detachment is critical because it frees up the carrier thread to run other Virtual Threads instead of being idle, waiting for the blocking operation to complete. As a result, a single carrier thread can multiplex many Virtual Threads, which could number in the thousands or even millions, depending on the available memory and the nature of tasks performed.

Let’s try to simulate this behavior. To do this, we will force our code to use only one CPU core, with only 2 virtual threads – for better clarity.

System.setProperty("jdk.virtualThreadScheduler.parallelism", "1");
System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "1");
System.setProperty("jdk.virtualThreadScheduler.minRunnable", "1");

Thread 1:

Thread v1 = Thread.ofVirtual().name("long-running-thread").start(
       () -> {
           var thread = Thread.currentThread();
           while (true) {
               try {
                   Thread.sleep(250L);
                   System.out.println(STR."[\{thread}] - Handling http request ....");
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       }
);

Thread 2:

Thread v2 = Thread.ofVirtual().name("entertainment-thread").start(
       () -> {
           try {
               Thread.sleep(1000L);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
           var thread = Thread.currentThread();
           System.out.println(STR."[\{thread}] - Executing when 'http-thread' hit 'sleep' function");
       }
);

Execution:

v1.join();
v2.join();

Result:

[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#23,entertainment-thread]/runnable@ForkJoinPool-1-worker-1] - Executing when 'http-thread' hit 'sleep' function
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....
[VirtualThread[#21,long-running-thread]/runnable@ForkJoinPool-1-worker-1] - Handling http request ....

We observe that both Virtual Threads (long-running-thread and entertainment-thread) are being carried by only one Platform Thread which is ForkJoinPool-1-worker-1.

To summarize, this model enables Java applications to achieve high levels of concurrency and scalability with much lower overhead than traditional thread models, where each thread maps directly to a single operating system thread. It’s worth noting that virtual threads are a vast topic, and what I’ve described is only a small fraction. I strongly encourage you to learn more about the scheduling, pinned threads and the internals of VirtualThreads.

Summary: The future of the Java programming language

The features described above are the ones I consider to be the most important in Java 21. Most of them aren’t as groundbreaking as some of the things introduced in JDK 17, but they’re still very useful, and nice to have QOL (Quality of Life) changes.

However, you shouldn’t discount other JDK 21 improvements either – I highly encourage you to analyze the complete list and explore all the features further. For example, one thing I consider particularly noteworthy is the Vector API, which allows vector computations on some supported CPU architectures – not possible before. Currently, it’s still in the incubator status/experimental phase (which is why I didn’t highlight it in more detail here), but it holds great promise for the future of Java.

Overall, the advancement Java made in various areas signals the team’s ongoing commitment to improving efficiency and performance in high-demand applications.

If you’re interested in Java, be sure to check out some of our other articles:

  1. Java 17 features: A comparison between versions 8 and 17. What has changed over the years?
  2. JVM Kubernetes: Optimizing Kubernetes for Java Developers
  3. Project Valhalla – Java on the path to better performance
  4. Advanced Java interview questions: A guide for 2023

Java features FAQ

Here are answers to some common questions regarding JDK 21, as well as Java native interface and features.

What is Java SE?

Java SE (Java Platform, Standard Edition) is a fundamental platform for developing and deploying Java applications on desktops and servers.

What is the Foreign Function and Memory API?

It’s a preview feature that lets Java programs interoperate with data and code outside the Java runtime. The API enables Java programs to call native libraries and process native data more safely than in the case of JNI. The API is a tool for safely accessing foreign memory and code, and efficiently invoking foreign functions.

How to write Java code well?

One of the key aspects is code review (you can use AI code review tools to make this process a bit less time-consuming).

What is dynamic loading in Java?

Dynamic loading in Java refers to loading classes or resources at runtime, rather than during the initial program startup.

What is structured concurrency?

Structured concurrency in Java is an approach that organizes concurrent processes in a controlled manner, aiming to enhance the maintainability, reliability, and observability of multithreaded code.

版本聲明 本文轉載自:https://dev.to/aroso1991/java-21-features-a-detailed-look-at-the-most-important-changes-in-the-new-lts-release-90o?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何在不修改HTML的情況下用JavaScript監聽表單提交事件?
    如何在不修改HTML的情況下用JavaScript監聽表單提交事件?
    在JavaScript 中監聽表單提交事件而不修改HTML在本文中,我們解決了在不修改HTML 的情況下監聽表單提交事件的常見挑戰必須修改HTML 程式碼。我們不依賴 HTML 中的 onClick 或 onSubmit 屬性,而是提供純 JavaScript 解決方案。 為了實現這一點,我們利用 ...
    程式設計 發佈於2024-11-02
  • Document.getElementById 與 jQuery $():主要差異是什麼?
    Document.getElementById 與 jQuery $():主要差異是什麼?
    Document.getElementById vs jQuery $():比較分析深入研究Web 開發領域時,了解一般版本之間的細微差別JavaScript 和jQuery 可能至關重要。本文研究了兩個看似相同的程式碼片段之間的細微差別:var contents = document.getEle...
    程式設計 發佈於2024-11-02
  • 在 Java 中使用方法和變數句柄進行運行時物件存取和操作
    在 Java 中使用方法和變數句柄進行運行時物件存取和操作
    反射和方法/var 句柄是 Java 中的兩個強大功能,允許開發人員在運行時存取和操作物件。然而,它們在存取和處理物件的方式上有所不同。 讓我們來看一個如何使用反射來存取類別中方法的範例。我們將從一個名為「MyClass」的簡單類別開始,它有一個私有字串變數和該變數的 getter 方法。為了創建...
    程式設計 發佈於2024-11-02
  • 如何在 Python 中使用內建函數驗證 IP 位址?
    如何在 Python 中使用內建函數驗證 IP 位址?
    Python 中的 IP 位址驗證驗證 IP 位址的有效性是程式設計中的常見任務。從使用者接收字串形式的 IP 位址時,必須對其進行驗證,以確保它們符合正確的格式和結構。 要在 Python 中有效驗證 IP 位址,請考慮以下方法:無需手動解析 IP 位址,而是利用套接字模組中的內建 inet_at...
    程式設計 發佈於2024-11-02
  • 我需要學習程式設計的幫助
    我需要學習程式設計的幫助
    您好,我是系統工程專業的學生,我覺得我在課程中學到的程式設計知識不多。我想自學,因為我對這個主題非常感興趣。這就是我在這個網站上向了解程式設計的人尋求幫助的原因。如果有人知道學習程式設計的最佳課程,從基礎開始並進步到更專業的水平,那將會有很大的幫助。 我感興趣的語言: Java JavaScrip...
    程式設計 發佈於2024-11-02
  • 如何將 gorm.Model 整合到具有日期時間支援的 Protocol Buffer 定義中?
    如何將 gorm.Model 整合到具有日期時間支援的 Protocol Buffer 定義中?
    將gorm.Model 整合到Protocol Buffer 定義中將gorm.Model 整合到Protocol Buffer 定義中將gorm 的gorm.Model 欄位整合到protobuf 定義時,由於proto3 中缺乏日期時間支持,出現了挑戰。本文探討了此問題的解決方案。 ProtoB...
    程式設計 發佈於2024-11-02
  • 修補您的 Discord 活動的網路請求,以實現順利的 CSP 合規性
    修補您的 Discord 活動的網路請求,以實現順利的 CSP 合規性
    透過Discord運行Discord活動時,您可能會遇到內容安全策略(CSP)問題。您可以透過確保網路請求遵循 Discord 代理 規則來修復這些問題。 這可以手動完成...或者你可以讓@robojs/patch處理它。 什麼是CSP? 內容安全策略 (CSP) 是一種安全標準...
    程式設計 發佈於2024-11-02
  • 推薦項目:刪除課程表查看數據
    推薦項目:刪除課程表查看數據
    LabEx 的這個專案釋放了資料庫管理的力量,提供了在資料庫中建立和操作視圖的全面學習體驗。無論您是嶄露頭角的資料庫管理員還是經驗豐富的開發人員,該專案都提供了寶貴的機會來增強您的技能並獲得對資料管理世界的實際見解。 深入了解基礎知識 在這個專案中,您將踏上了解資料庫中視圖的核心概...
    程式設計 發佈於2024-11-02
  • 模擬網路請求變得容易:整合 Jest 和 MSW
    模擬網路請求變得容易:整合 Jest 和 MSW
    Writing unit tests that involve mocking or stubbing API calls can feel overwhelming—I’ve been there myself. In this article, I’ll guide you through a ...
    程式設計 發佈於2024-11-02
  • 使用 Javascript 的哈希映射
    使用 Javascript 的哈希映射
    介紹 哈希映射(Hash Map),也稱為哈希表(Hash Table),是實現關聯數組抽象資料類型的資料結構,是可以將鍵映射到值的結構。 它使用雜湊函數來計算儲存桶或槽數組的索引,從中可以找到所需的值。 哈希映射的主要優點是它的效率。插入新的鍵值對、刪除鍵值對以及查找給定鍵...
    程式設計 發佈於2024-11-02
  • HTPX 簡介:適用於 JavaScript 和 Node.js 的輕量級多功能 HTTP 用戶端
    HTPX 簡介:適用於 JavaScript 和 Node.js 的輕量級多功能 HTTP 用戶端
    作為開發人員,我們的Web 應用程式通常需要一個可靠且高效的HTTP 用戶端,無論我們是在瀏覽器中使用JavaScript 還是在伺服器端使用Node.js 進行構建。這就是我創建 HTPX 的原因——一個強大的、輕量級的解決方案,旨在簡化 HTTP 請求,同時為現代開發提供一系列功能。 在本文中...
    程式設計 發佈於2024-11-02
  • 使用自然語言透過法學碩士產生簡單的 Python GUI .... 在不到幾分鐘的時間內
    使用自然語言透過法學碩士產生簡單的 Python GUI .... 在不到幾分鐘的時間內
    Thought that building Python GUIs took hours of tedious coding? Welcome to an exciting new era! Not only can tools like Github Copilot help with code ...
    程式設計 發佈於2024-11-02
  • Dev、Oops 和 WEBAPP 故事
    Dev、Oops 和 WEBAPP 故事
    作為 DevOps 專業人員開發桌面 Web 應用程式感覺就像在廣闊而複雜的海洋中航行。隨著技術融合,Web、桌面和基於雲端的應用程式之間的界限變得模糊,迫使 DevOps 深入傳統上由前端佔據的領域 終端開發商。選擇正確的框架變得至關重要,但挑戰往往在於篩選當今可用的眾多選項。例如,Vite、Re...
    程式設計 發佈於2024-11-02
  • 釋放您的 Django 潛力:適合 4 人的專案創意和資源
    釋放您的 Django 潛力:適合 4 人的專案創意和資源
    Django 電子報 - 2024 年 10 月 Django 簡介與專案想法 如果您希望開始使用 Django 或提高自己的技能,請考慮以下一些寶貴的資源和專案想法: Django 專案想法 對於想要嘗試或建立自己的作品集的人來說,Django 提...
    程式設計 發佈於2024-11-02
  • 我該如何修復 TypeScript 設定問題:類型請求中不存在屬性使用者。
    我該如何修復 TypeScript 設定問題:類型請求中不存在屬性使用者。
    問題 我一直在使用 TypeScript 和 Express.js 開發 Node.js 專案。有一次,我需要將一個使用者物件附加到 Express Request 對象,但我遇到了這個 TypeScript 錯誤: 類型「Request」上不存在屬性「user」。 我很快意識到發生這種情況是因為...
    程式設計 發佈於2024-11-02

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3