”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Java 功能:详细了解新 LTS 版本中最重要的变化

Java 功能:详细了解新 LTS 版本中最重要的变化

发布于2024-07-29
浏览:636

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]删除
最新教程 更多>
  • PHP阵列键值异常:了解07和08的好奇情况
    PHP阵列键值异常:了解07和08的好奇情况
    PHP数组键值问题,使用07&08 在给定数月的数组中,键值07和08呈现令人困惑的行为时,就会出现一个不寻常的问题。运行print_r($月份)返回意外结果:键“ 07”丢失,而键“ 08”分配给了9月的值。此问题源于PHP对领先零的解释。当一个数字带有0(例如07或08)的前缀时,PHP将...
    编程 发布于2025-03-10
  • 为什么我的CSS背景图像出现?
    为什么我的CSS背景图像出现?
    故障排除:CSS背景图像未出现 ,您的背景图像尽管遵循教程说明,但您的背景图像仍未加载。图像和样式表位于相同的目录中,但背景仍然是空白的白色帆布。而不是不弃用的,您已经使用了CSS样式: bockent {背景:封闭图像文件名:背景图:url(nickcage.jpg); 如果您的html,css...
    编程 发布于2025-03-10
  • 如何克服PHP的功能重新定义限制?
    如何克服PHP的功能重新定义限制?
    克服PHP的函数重新定义限制在PHP中,多次定义一个相同名称的函数是一个no-no。尝试这样做,如提供的代码段所示,将导致可怕的“不能重新列出”错误。 但是,PHP工具腰带中有一个隐藏的宝石:runkit扩展。它使您能够灵活地重新定义函数。 runkit_function_renction_re...
    编程 发布于2025-03-10
  • 如何使用PHP从XML文件中有效地检索属性值?
    如何使用PHP从XML文件中有效地检索属性值?
    从php $xml = simplexml_load_file($file); foreach ($xml->Var[0]->attributes() as $attributeName => $attributeValue) { echo $attributeName,...
    编程 发布于2025-03-10
  • 为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    The Mystery of "Broken" Two-Phase Template Instantiation in Microsoft Visual C Problem Statement:Users commonly express concerns that Micro...
    编程 发布于2025-03-10
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中3个Party Package将另一个PAXPANCE带有导入式套件之间的另一个软件包,并在导入式套件之间导入另一个软件包。如回声消息所证明的那样: go.etcd.io/bbolt [&&&&&&&&&&&&&&&&...
    编程 发布于2025-03-10
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-03-10
  • 如何限制动态大小的父元素中元素的滚动范围?
    如何限制动态大小的父元素中元素的滚动范围?
    在交互式接口中实现垂直滚动元素的CSS高度限制问题:考虑一个布局,其中我们具有与用户垂直滚动一起移动的可滚动地图div,同时与固定的固定sidebar保持一致。但是,地图的滚动无限期扩展,超过了视口的高度,阻止用户访问页面页脚。$("#map").css({ marginT...
    编程 发布于2025-03-10
  • 如何修复\“常规错误:2006 MySQL Server在插入数据时已经消失\”?
    如何修复\“常规错误:2006 MySQL Server在插入数据时已经消失\”?
    How to Resolve "General error: 2006 MySQL server has gone away" While Inserting RecordsIntroduction:Inserting data into a MySQL database can...
    编程 发布于2025-03-10
  • 我可以将加密从McRypt迁移到OpenSSL,并使用OpenSSL迁移MCRYPT加密数据?
    我可以将加密从McRypt迁移到OpenSSL,并使用OpenSSL迁移MCRYPT加密数据?
    将我的加密库从mcrypt升级到openssl 问题:是否可以将我的加密库从McRypt升级到OpenSSL?如果是这样,如何?答案:是的,可以将您的Encryption库从McRypt升级到OpenSSL。可以使用openssl。附加说明: [openssl_decrypt()函数要求iv参...
    编程 发布于2025-03-10
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-03-10
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-03-10
  • 如何使用组在MySQL中旋转数据?
    如何使用组在MySQL中旋转数据?
    在关系数据库中使用mySQL组使用mySQL组进行查询结果,在关系数据库中使用MySQL组,转移数据的数据是指重新排列的行和列的重排以增强数据可视化。在这里,我们面对一个共同的挑战:使用组的组将数据从基于行的基于列的转换为基于列。让我们考虑以下查询: select data d.data_ti...
    编程 发布于2025-03-10
  • 如何在JavaScript对象中动态设置键?
    如何在JavaScript对象中动态设置键?
    在尝试为JavaScript对象创建动态键时,如何使用此Syntax jsObj['key' i] = 'example' 1;不工作。正确的方法采用方括号: jsobj ['key''i] ='example'1; 在JavaScript中,数组是一...
    编程 发布于2025-03-10

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3