Rumah  >  Artikel  >  Java  >  Ciri Java: Pandangan terperinci tentang perubahan paling penting dalam keluaran LTS baharu

Ciri Java: Pandangan terperinci tentang perubahan paling penting dalam keluaran LTS baharu

WBOY
WBOYasal
2024-07-29 20:39:141019semak imbas

Satu lagi keluaran Java LTS sudah ada, membawa beberapa perubahan dan penambahbaikan yang menarik. Mari analisa ciri Java 21 yang paling penting, lihat cara ia berfungsi dalam amalan dan cuba ramalkan kepentingannya untuk masa depan teknologi ini.

Memandangkan platform Java menggunakan kitaran keluaran enam bulan, kami telah melepasi soalan-soalan berterusan seperti "Adakah Java akan mati tahun ini?" atau "Adakah ia berbaloi untuk berhijrah ke versi baharu?". Walaupun 28 tahun sejak keluaran pertama, Java terus berkembang maju dan kekal sebagai pilihan popular sebagai bahasa pengaturcaraan utama untuk banyak projek baharu.

Java 17 merupakan peristiwa penting, tetapi Java 21 kini telah mengambil tempat ke-17 sebagai keluaran sokongan jangka panjang (LTS) seterusnya. Adalah penting bagi pembangun Java untuk terus mendapat maklumat tentang perubahan dan ciri baharu yang dibawa oleh versi ini. Diilhamkan oleh rakan sekerja saya Darek, yang memperincikan ciri Java 17 dalam artikelnya, saya telah memutuskan untuk membincangkan JDK 21 dengan cara yang sama.

JDK 21 terdiri daripada 15 JEP (Cadangan Peningkatan JDK). Anda boleh menyemak senarai lengkap di tapak Java rasmi. Dalam artikel ini, saya akan menyerlahkan beberapa JEP Java 21 yang saya percaya amat perlu diberi perhatian. Iaitu:

  1. Templat Rentetan
  2. Koleksi Berjujukan
  3. Padanan Corak untuk suis dan Corak Rekod
  4. Benang Maya

Tanpa berlengah lagi, mari kita teliti kod dan terokai kemas kini ini.

Templat Rentetan (Pratonton)

Ciri Templat Spring masih dalam mod pratonton. Untuk menggunakannya, anda perlu menambah bendera –enable-preview pada args pengkompil anda. Walau bagaimanapun, saya telah memutuskan untuk menyebutnya walaupun status pratontonnya. kenapa? Kerana saya sangat jengkel setiap kali saya perlu menulis mesej log atau pernyataan sql yang mengandungi banyak hujah atau menguraikan pemegang tempat mana yang akan digantikan dengan arg yang diberikan. Dan Templat Musim Bunga berjanji untuk membantu saya (dan anda) dengan itu.

Seperti yang dinyatakan oleh dokumentasi JEP, tujuan Templat Spring adalah untuk "memudahkan penulisan program Java dengan memudahkan untuk menyatakan rentetan yang termasuk nilai yang dikira pada masa jalankan".

Mari periksa sama ada ia benar-benar lebih mudah.

"Cara lama" ialah menggunakan kaedah formatted() pada objek String:

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

Kini, dengan StringTemplate.Processor (STR), ia kelihatan seperti ini:

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

Dengan teks pendek seperti di atas, keuntungan mungkin tidak begitu ketara – tetapi percayalah, apabila ia berkaitan dengan blok teks besar (jsons, pernyataan sql), parameter bernama akan banyak membantu anda.

Koleksi berurutan

Java 21 memperkenalkan Hierarki Koleksi Java baharu. Lihat rajah di bawah dan bandingkan dengan apa yang mungkin anda pelajari semasa kelas pengaturcaraan anda. Anda akan perasan bahawa tiga struktur baharu telah ditambah (diserlahkan oleh warna hijau).

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

Koleksi berjujukan memperkenalkan API Java terbina dalam baharu, meningkatkan operasi pada set data tersusun. API ini bukan sahaja membenarkan akses mudah kepada elemen pertama dan terakhir koleksi tetapi juga membolehkan traversal yang cekap, sisipan pada kedudukan tertentu dan mendapatkan semula sub-jujukan. Peningkatan ini menjadikan operasi yang bergantung pada susunan elemen lebih mudah dan lebih intuitif, meningkatkan prestasi dan kebolehbacaan kod apabila bekerja dengan senarai dan struktur data yang serupa.

Ini ialah senarai penuh antara muka SequencedCollection:

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;
   }
}

Jadi, sekarang, bukannya:

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

Kami hanya boleh menulis:

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

Perubahan kecil, tetapi IMHO ia adalah ciri yang mudah dan boleh digunakan.

Padanan Corak dan Corak Rekod

Oleh kerana persamaan Padanan Corak untuk suis dan Corak Rekod, saya akan menerangkannya bersama-sama. Corak rekod ialah ciri baharu – ia telah diperkenalkan di Java 19 (sebagai pratonton). Sebaliknya, Padanan Corak untuk suis agak kesinambungan daripada ungkapan contoh lanjutan. Ia membawa masuk kemungkinan sintaks baharu untuk pernyataan suis yang membolehkan anda menyatakan pertanyaan berorientasikan data yang kompleks dengan lebih mudah.

Mari kita lupakan asas OOP demi contoh ini dan nyahbina objek pekerja secara manual (pekerja ialah kelas POJO).

Sebelum Java 21, Ia kelihatan seperti ini:

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);
}

Bagaimana jika kita boleh menyingkirkan contoh yang hodoh? Nah, kini kita boleh, terima kasih kepada kuasa Padanan Corak dari 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 < 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
}

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.

Ringkasan: Masa depan bahasa pengaturcaraan Java

Ciri-ciri yang diterangkan di atas ialah ciri yang saya anggap paling penting dalam Java 21. Kebanyakannya tidak begitu hebat seperti beberapa perkara yang diperkenalkan dalam JDK 17, tetapi ia masih sangat berguna dan bagus untuk mempunyai perubahan QOL (Kualiti Kehidupan).

Walau bagaimanapun, anda juga tidak seharusnya menolak penambahbaikan JDK 21 yang lain - saya amat menggalakkan anda untuk menganalisis senarai lengkap dan meneroka semua ciri dengan lebih lanjut. Sebagai contoh, satu perkara yang saya anggap amat perlu diberi perhatian ialah API Vektor, yang membenarkan pengiraan vektor pada beberapa seni bina CPU yang disokong – tidak mungkin sebelum ini. Pada masa ini, ia masih dalam status inkubator/fasa eksperimen (itulah sebabnya saya tidak menyerlahkannya dengan lebih terperinci di sini), tetapi ia menjanjikan masa depan Java.

Secara keseluruhannya, kemajuan Java dalam pelbagai bidang menandakan komitmen berterusan pasukan untuk meningkatkan kecekapan dan prestasi dalam aplikasi permintaan tinggi.

Jika anda berminat dengan Java, pastikan anda menyemak beberapa artikel kami yang lain:

  1. Ciri Java 17: Perbandingan antara versi 8 dan 17. Apakah yang telah berubah selama ini?
  2. JVM Kubernetes: Mengoptimumkan Kubernetes untuk Pembangun Java
  3. Projek Valhalla – Java menuju ke arah prestasi yang lebih baik
  4. Soalan temu bual Java lanjutan: Panduan untuk 2023

Soalan lazim ciri Java

Berikut ialah jawapan kepada beberapa soalan lazim mengenai JDK 21, serta antara muka dan ciri asli Java.

Apakah Java SE?

Java SE (Platform Java, Edisi Standard) ialah platform asas untuk membangunkan dan menggunakan aplikasi Java pada desktop dan pelayan.

Apakah Fungsi Asing dan API Memori?

Ia adalah ciri pratonton yang membolehkan program Java beroperasi dengan data dan kod di luar masa jalan Java. API membolehkan program Java untuk memanggil perpustakaan asli dan memproses data asli dengan lebih selamat daripada dalam kes JNI. API ialah alat untuk mengakses memori dan kod asing dengan selamat, serta menggunakan fungsi asing dengan cekap.

Bagaimana untuk menulis kod Java dengan baik?

Salah satu aspek utama ialah semakan kod (anda boleh menggunakan alatan semakan kod AI untuk menjadikan proses ini kurang memakan masa).

Apakah pemuatan dinamik dalam Java?

Pemuatan dinamik dalam Java merujuk kepada memuatkan kelas atau sumber pada masa jalanan, bukannya semasa permulaan program awal.

Apakah keselarasan berstruktur?

Konkurensi berstruktur dalam Java ialah pendekatan yang mengatur proses serentak dengan cara terkawal, bertujuan untuk meningkatkan kebolehselenggaraan, kebolehpercayaan dan kebolehmerhatian kod berbilang benang.

Atas ialah kandungan terperinci Ciri Java: Pandangan terperinci tentang perubahan paling penting dalam keluaran LTS baharu. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn