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

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

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

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]刪除
最新教學 更多>
  • 在 Go 中使用 WebSocket 進行即時通信
    在 Go 中使用 WebSocket 進行即時通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要一种比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSoc...
    程式設計 發佈於2024-12-18
  • Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta 中的列偏移發生了什麼事?
    Bootstrap 4 Beta:列偏移的刪除和恢復Bootstrap 4 在其Beta 1 版本中引入了重大更改柱子偏移了。然而,隨著 Beta 2 的後續發布,這些變化已經逆轉。 從 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    程式設計 發佈於2024-12-18
  • 如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    如何在 PHP 中組合兩個關聯數組,同時保留唯一 ID 並處理重複名稱?
    在 PHP 中組合關聯數組在 PHP 中,將兩個關聯數組組合成一個數組是常見任務。考慮以下請求:問題描述:提供的代碼定義了兩個關聯數組,$array1 和 $array2。目標是建立一個新陣列 $array3,它合併兩個陣列中的所有鍵值對。 此外,提供的陣列具有唯一的 ID,而名稱可能重疊。要求是建...
    程式設計 發佈於2024-12-18
  • 插入資料時如何修復「常規錯誤:2006 MySQL 伺服器已消失」?
    插入資料時如何修復「常規錯誤:2006 MySQL 伺服器已消失」?
    插入記錄時如何解決「一般錯誤:2006 MySQL 伺服器已消失」介紹:將資料插入MySQL 資料庫有時會導致錯誤「一般錯誤:2006 MySQL 伺服器已消失」。當與伺服器的連線遺失時會出現此錯誤,通常是由於 MySQL 配置中的兩個變數之一所致。 解決方案:解決此錯誤的關鍵是調整wait_tim...
    程式設計 發佈於2024-12-18
  • 大批
    大批
    方法是可以在物件上呼叫的 fns 數組是對象,因此它們在 JS 中也有方法。 slice(begin):將陣列的一部分提取到新數組中,而不改變原始數組。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index ...
    程式設計 發佈於2024-12-18
  • 儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    儘管程式碼有效,為什麼 POST 請求無法擷取 PHP 中的輸入?
    解決PHP 中的POST 請求故障在提供的程式碼片段:action=''而非:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"檢查$_POST陣列:表單提交後使用 var_dump 檢查 $_POST 陣列的內容...
    程式設計 發佈於2024-12-18
  • CSS3 轉場是否提供事件來偵測起點和終點?
    CSS3 轉場是否提供事件來偵測起點和終點?
    了解 CSS3 過渡事件CSS3 過渡允許在 Web 元素上實現流暢的動畫和視覺效果。為了增強使用者體驗並使操作與這些轉換同步,監控其進度非常重要。本文解決了 CSS3 是否提供事件來檢查過渡何時開始或結束的問題。 W3C CSS 過渡草案W3C CSS 過渡草案規定CSS 轉換會觸發對應的 DOM...
    程式設計 發佈於2024-12-18
  • Java 中可以手動釋放記憶體嗎?
    Java 中可以手動釋放記憶體嗎?
    Java 中的手動內存釋放與垃圾回收與C 不同,Java 採用託管內存框架來處理內存分配和釋放由垃圾收集器(GC) 自動執行。這種自動化方法可以提高記憶體利用率並防止困擾 C 程式的記憶體洩漏。 Java 中可以手動釋放記憶體嗎? 由於 Java 的記憶體管理是由GC,它沒有提供像 C 中的 fre...
    程式設計 發佈於2024-12-18
  • Java 1.6 中如何可靠地確定檔案是否為符號連結?
    Java 1.6 中如何可靠地確定檔案是否為符號連結?
    在 Java 1.6 中驗證符號連結確定符號連結的存在對於各種文件處理操作至關重要。在 Java 中,識別符號連結時需要考慮一些潛在問題,特別是在目錄遍歷的上下文中。 檢查符號連結的常見方法是比較文件的絕對路徑和規範路徑。規範路徑表示檔案的標準化路徑,而絕對路徑可能包括符號連結。傳統上,概念是如果這...
    程式設計 發佈於2024-12-17
  • 如何使背景顏色透明,同時保持文字不透明?
    如何使背景顏色透明,同時保持文字不透明?
    背景顏色的不透明度而不影響文本在Web 開發領域,實現透明度通常對於增強視覺吸引力和網站元素的功能。常見的要求是對 div 背景套用透明度,同時保留所包含文字的不透明度。這可能會帶來挑戰,特別是在確保跨瀏覽器相容性方面。 rgba 解決方案最有效且廣泛支持的解決方案是利用「RGBA」(紅、綠、藍、A...
    程式設計 發佈於2024-12-17
  • PHP 字串比較:`==`、`===` 或 `strcmp()` – 您應該使用哪個運算子?
    PHP 字串比較:`==`、`===` 或 `strcmp()` – 您應該使用哪個運算子?
    PHP 中的字串比較:'=='、'===' 或 'strcmp()'? PHP 中的字串比較PHP 可以使用不同的運算子來完成,例如「==」、「===」或「strcmp()」函數。此比較涉及檢查兩個字串是否相等。 '==' 與'...
    程式設計 發佈於2024-12-17
  • 如何自訂操作列的按鈕和外觀?
    如何自訂操作列的按鈕和外觀?
    自訂操作欄的按鈕和外觀要實現所需的自訂操作欄外觀,請考慮以下步驟: 1.建立自訂操作按鈕若要將圖片包含為按鈕,請透過擴充Button類別來定義自訂視圖。然後可以將此自訂視圖顯示在 ActionBar 上,如下所示:<Button android:id="@ id/my_cus...
    程式設計 發佈於2024-12-17
  • 介紹 Laravel 的履歷解析器/CV 解析器
    介紹 Laravel 的履歷解析器/CV 解析器
    照片由 Mohammad Rahmani 在 Unsplash 上拍攝 基於我們的 Resume/CV Parsing AI API 端點的流行,我們專門為您製作了一個專門的輕量級 Laravel 庫。 招募的未來:敏銳、精確且對 Laravel 友好 這個新套件可在 github...
    程式設計 發佈於2024-12-17
  • 如何在 PHP 中重新格式化日期以方便使用者顯示?
    如何在 PHP 中重新格式化日期以方便使用者顯示?
    在PHP 中重新格式化日期使用資料庫中儲存的日期時,通常需要重新格式化它們以便於使用者友好的顯示。對於以「2009-08-12」等格式儲存的日期尤其如此,人類本質上無法讀取這種格式。 為了解決這個問題,PHP 提供了各種工具,使您能夠輕鬆重新格式化日期。一種有效的方法是使用 DateTime 類,它...
    程式設計 發佈於2024-12-17
  • 為什麼我無法將元素新增到具有通配符泛型類型(`?extends Parent`)的 Java 集合中?
    為什麼我無法將元素新增到具有通配符泛型類型(`?extends Parent`)的 Java 集合中?
    型安與通配符泛型:了解禁止修飾符在Java 使用泛型集合時,通配符泛型的概念可以引入某些最初看起來可能違反直覺的限制。一個典型的例子是無法在具有通配符泛型類型的 Java 集合中新增值。 考慮以下程式碼片段:List<? extends Parent> list = ...; Paren...
    程式設計 發佈於2024-12-17

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

Copyright© 2022 湘ICP备2022001581号-3