Eine weitere LTS-Java-Version ist bereits da und bringt einige spannende Änderungen und Verbesserungen mit sich. Lassen Sie uns die wichtigsten Funktionen von Java 21 analysieren, sehen, wie sie in der Praxis funktionieren, und versuchen, ihre Bedeutung für die Zukunft dieser Technologie vorherzusagen.
Seit die Java-Plattform einen sechsmonatigen Veröffentlichungszyklus eingeführt hat, haben wir die ewigen Fragen wie „Wird Java dieses Jahr sterben?“ hinter uns gelassen. oder „Lohnt sich die Migration auf die neue Version?“. Trotz 28 Jahren seit seiner ersten Veröffentlichung floriert Java weiterhin und bleibt eine beliebte Wahl als primäre Programmiersprache für viele neue Projekte.
Java 17 war ein bedeutender Meilenstein, aber Java 21 hat nun den Platz von 17 als nächstes Long-Term-Support-Release (LTS) eingenommen. Für Java-Entwickler ist es wichtig, über die Änderungen und neuen Funktionen dieser Version auf dem Laufenden zu bleiben. Inspiriert von meinem Kollegen Darek, der in seinem Artikel die Funktionen von Java 17 detailliert beschrieben hat, habe ich beschlossen, JDK 21 auf ähnliche Weise zu diskutieren.
JDK 21 umfasst insgesamt 15 JEPs (JDK Enhancement Proposals). Sie können die vollständige Liste auf der offiziellen Java-Website einsehen. In diesem Artikel werde ich einige Java 21-JEPs hervorheben, die meiner Meinung nach besonders bemerkenswert sind. Nämlich:
Lassen Sie uns ohne weitere Verzögerung in den Code eintauchen und diese Updates erkunden.
Die Funktion „Frühlingsvorlagen“ befindet sich noch im Vorschaumodus. Um es zu verwenden, müssen Sie das Flag –enable-preview zu Ihren Compiler-Argumenten hinzufügen. Ich habe mich jedoch entschieden, es trotz seines Vorschaustatus zu erwähnen. Warum? Weil ich jedes Mal sehr irritiert bin, wenn ich eine Protokollnachricht oder eine SQL-Anweisung schreiben muss, die viele Argumente enthält, oder entschlüsseln muss, welcher Platzhalter durch ein bestimmtes Argument ersetzt wird. Und Spring Templates versprechen, mir (und Ihnen) dabei zu helfen.
Wie es in der JEP-Dokumentation heißt, besteht der Zweck von Spring Templates darin, „das Schreiben von Java-Programmen zu vereinfachen, indem es einfach gemacht wird, Zeichenfolgen auszudrücken, die zur Laufzeit berechnete Werte enthalten“.
Lassen Sie uns prüfen, ob es wirklich einfacher ist.
Der „alte Weg“ wäre die Verwendung der Methode formatted() für ein String-Objekt:
var msg = "Log message param1: %s, pram2: %s".formatted(p1, p2);
Jetzt sieht es mit StringTemplate.Processor (STR) so aus:
var interpolated = STR."Log message param1: \{p1}, param2: \{p2}";
Bei einem kurzen Text wie dem oben genannten ist der Gewinn vielleicht nicht so sichtbar – aber glauben Sie mir, wenn es um große Textblöcke (jsons, sql-Anweisungen) geht, werden Ihnen benannte Parameter sehr helfen.
Java 21 führte eine neue Java-Sammlungshierarchie ein. Schauen Sie sich das Diagramm unten an und vergleichen Sie es mit dem, was Sie wahrscheinlich während Ihres Programmierunterrichts gelernt haben. Sie werden feststellen, dass drei neue Strukturen hinzugefügt wurden (hervorgehoben durch die grüne Farbe).
Quelle: JEP 431
Sequenzierte Sammlungen führen eine neue integrierte Java-API ein, die den Betrieb geordneter Datensätze verbessert. Diese API ermöglicht nicht nur den bequemen Zugriff auf das erste und letzte Element einer Sammlung, sondern ermöglicht auch eine effiziente Durchquerung, das Einfügen an bestimmten Positionen und das Abrufen von Teilsequenzen. Diese Verbesserungen machen Vorgänge, die von der Reihenfolge der Elemente abhängen, einfacher und intuitiver und verbessern sowohl die Leistung als auch die Lesbarkeit des Codes bei der Arbeit mit Listen und ähnlichen Datenstrukturen.
Dies ist die vollständige Auflistung der SequencedCollection-Schnittstelle:
public interface SequencedCollection<E> extends Collection<E> { SequencedCollection<E> 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, jetzt statt:
var first = myList.stream().findFirst().get(); var anotherFirst = myList.get(0); var last = myList.get(myList.size() - 1);
Wir können einfach schreiben:
var first = sequencedCollection.getFirst(); var last = sequencedCollection.getLast(); var reversed = sequencedCollection.reversed();
Eine kleine Änderung, aber meiner Meinung nach ist es eine so praktische und benutzerfreundliche Funktion.
Aufgrund der Ähnlichkeit des Mustervergleichs für Schalter und Aufnahmemuster werde ich sie zusammen beschreiben. Datensatzmuster sind eine neue Funktion – sie wurden in Java 19 eingeführt (als Vorschau). Andererseits ist Pattern Matching für switch eine Art Fortsetzung der erweiterten Ausdrucksinstanz. Es führt eine neue mögliche Syntax für Switch-Anweisungen ein, mit der Sie komplexe datenorientierte Abfragen einfacher ausdrücken können.
Lassen Sie uns für dieses Beispiel die Grundlagen von OOP vergessen und das Mitarbeiterobjekt manuell dekonstruieren (Mitarbeiter ist eine POJO-Klasse).
Vor Java 21 sah es so aus:
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); }
Was wäre, wenn wir die hässliche instanceof loswerden könnten? Dank der Leistungsfähigkeit von Pattern Matching von Java 21 ist das jetzt möglich:
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 < 0 || b < 0 -> System.out.printf("Incorrect values for rectangle [%d, %d].%n", a, b); case Square(int a) when a < 0 -> 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 }
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:
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.
Die oben beschriebenen Funktionen sind meiner Meinung nach die wichtigsten in Java 21. Die meisten davon sind nicht so bahnbrechend wie einige der in JDK 17 eingeführten Dinge, aber sie sind dennoch sehr nützlich und angenehm Veränderungen der Lebensqualität (Quality of Life) haben.
Allerdings sollten Sie auch andere JDK 21-Verbesserungen nicht außer Acht lassen – ich empfehle Ihnen dringend, die vollständige Liste zu analysieren und alle Funktionen weiter zu erkunden. Besonders erwähnenswert finde ich zum Beispiel die Vector API, die Vektorberechnungen auf einigen unterstützten CPU-Architekturen ermöglicht – was vorher nicht möglich war. Derzeit befindet es sich noch in der Inkubator-/Experimentalphase (weshalb ich es hier nicht näher beleuchtet habe), aber es ist vielversprechend für die Zukunft von Java.
Insgesamt signalisieren die Fortschritte, die Java in verschiedenen Bereichen gemacht hat, das kontinuierliche Engagement des Teams für die Verbesserung der Effizienz und Leistung in stark nachgefragten Anwendungen.
Wenn Sie sich für Java interessieren, schauen Sie sich unbedingt einige unserer anderen Artikel an:
Hier finden Sie Antworten auf einige häufig gestellte Fragen zu JDK 21 sowie zur nativen Java-Schnittstelle und den Funktionen.
Java SE (Java Platform, Standard Edition) ist eine grundlegende Plattform für die Entwicklung und Bereitstellung von Java-Anwendungen auf Desktops und Servern.
Es handelt sich um eine Vorschaufunktion, die es Java-Programmen ermöglicht, mit Daten und Code außerhalb der Java-Laufzeitumgebung zu interagieren. Die API ermöglicht es Java-Programmen, native Bibliotheken aufzurufen und native Daten sicherer zu verarbeiten als im Fall von JNI. Die API ist ein Tool für den sicheren Zugriff auf fremden Speicher und Code sowie für den effizienten Aufruf fremder Funktionen.
Einer der Schlüsselaspekte ist die Codeüberprüfung (Sie können KI-Codeüberprüfungstools verwenden, um diesen Prozess etwas weniger zeitaufwändig zu gestalten).
Dynamisches Laden in Java bezieht sich auf das Laden von Klassen oder Ressourcen zur Laufzeit und nicht während des ersten Programmstarts.
Strukturierte Parallelität in Java ist ein Ansatz, der gleichzeitige Prozesse auf kontrollierte Weise organisiert und darauf abzielt, die Wartbarkeit, Zuverlässigkeit und Beobachtbarkeit von Multithread-Code zu verbessern.
Das obige ist der detaillierte Inhalt vonJava-Funktionen: Ein detaillierter Blick auf die wichtigsten Änderungen in der neuen LTS-Version. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!