Java 21 LTS – najważniejsze zmiany od Java 18 do Java 21

Co nowego w Java 21 LTS?

Java 21 to kolejna wersja Long-Term Support (LTS), która zastępuje Java 17 jako główne wydanie rekomendowane do zastosowań produkcyjnych. Oprócz poprawek wydajności i bezpieczeństwa wprowadza również szereg nowych funkcji, które upraszczają pisanie kodu i otwierają nowe możliwości przy projektowaniu aplikacji.

W tym artykule omówię najważniejsze zmiany, które pojawiły się w wydaniach Java 18, Java 19, Java 20 oraz Java 21. Skupię się przede wszystkim na nowościach, które mogą mieć realny wpływ na codzienną pracę programisty – od wirtualnych wątków (Virtual Threads), przez Pattern Matching i Record Patterns, aż po nowe kolekcje oraz usprawnienia maszyny wirtualnej.

Jeżeli obecnie korzystasz z Java 17 i zastanawiasz się, czy warto przejść na Java 21, ten artykuł pomoże Ci zrozumieć najważniejsze zmiany oraz korzyści wynikające z migracji

Java 18

Java 18 nie wprowadziła wielu przełomowych zmian w samym języku, jednak zawiera kilka usprawnień, które zwiększają przewidywalność działania aplikacji oraz ułatwiają codzienną pracę programistów.

UTF-8 jako domyślne kodowanie znaków

Jedną z najważniejszych zmian w Java 18 jest wprowadzenie UTF-8 jako domyślnego kodowania znaków (JEP 400). Do tej pory domyślne kodowanie zależało od systemu operacyjnego oraz jego konfiguracji. Przykładowo na systemach Windows często wykorzystywano kodowanie Windows-1250 lub Windows-1252, natomiast na większości dystrybucji Linux domyślnym kodowaniem było UTF-8.

Takie zachowanie mogło prowadzić do trudnych do zdiagnozowania problemów podczas odczytu lub zapisu plików. Aplikacja, która działała poprawnie na jednym systemie operacyjnym, mogła zwracać niepoprawne znaki po uruchomieniu na innym środowisku.

Od Java 18 domyślnym kodowaniem znaków jest UTF-8, niezależnie od systemu operacyjnego.

Lepsze przykłady kodu w dokumentacji API

Kolejną nowością jest wprowadzenie znacznika @snippet do dokumentacji Javadoc (JEP 413). Umożliwia on umieszczanie czytelnych przykładów kodu bezpośrednio w dokumentacji API.

Java 19

Java 19 została wydana jako kolejne wydanie o krótkim okresie wsparcia (non-LTS). Chociaż większość nowości miała jeszcze status Preview lub Incubator, to właśnie w tej wersji pojawiły się funkcje, które wyznaczyły kierunek rozwoju języka na kolejne lata. Najważniejszą z nich są Virtual Threads, będące częścią projektu Project Loom.

Virtual Threads (Preview)

Jedną z najważniejszych nowości w Java 19 jest wprowadzenie Virtual Threads (JEP 425) w wersji Preview. Ich celem jest umożliwienie uruchamiania ogromnej liczby współbieżnych zadań bez konieczności tworzenia takiej samej liczby kosztownych wątków systemowych. Więcej o nich wspomnę w wersji JDK, gdzie ta funkcjonalność została na stałe Dodona.

Record Patterns (Preview)

Java 19 wprowadziła również pierwszą wersję Record Patterns (JEP 405). Funkcja ta pozwala jednocześnie sprawdzić typ obiektu oraz rozpakować wartości przechowywane w rekordzie.

Structured Concurrency (Incubator)

Kolejną nowością jest Structured Concurrency (JEP 428), której celem jest uproszczenie zarządzania współbieżnymi zadaniami.

Java 20

Java 20 została wydana w marcu 2023 roku jako kolejne wydanie o krótkim okresie wsparcia (non-LTS). W przeciwieństwie do Java 19 nie wprowadziła wielu całkowicie nowych funkcji. Skupiono się przede wszystkim na rozwijaniu rozwiązań zaprezentowanych wcześniej.

Virtual Threads (Second Preview)

Java 20 wprowadziła drugą wersję podglądową Virtual Threads (JEP 436). Nie zmieniła ona sposobu korzystania z tej funkcji, ale zawierała liczne poprawki oraz usprawnienia wynikające z opinii społeczności po wydaniu Java 19.

Record Patterns (Second Preview)

Również wprowadzono drugą wersję podglądową Record Patterns (JEP 432).

Structured Concurrency (Second Incubator)

Podobnie jak w przypadku Virtual Threads skupiono się przede wszystkim na poprawkach.

Java 21

Java 21 została wydana we wrześniu 2023 roku i jest kolejną wersją Long-Term Support (LTS) platformy Java. To najważniejsze wydanie od czasu premiery Java 17, ponieważ wiele funkcji rozwijanych w poprzednich wersjach osiągnęło status stabilny i może być wykorzystywanych w aplikacjach produkcyjnych.

Największą nowością są Virtual Threads, które znacząco upraszczają tworzenie wysoko współbieżnych aplikacji. Oprócz tego Java 21 wprowadza między innymi finalne wersje Pattern Matching for switch oraz Record Patterns, nowe API dla kolekcji, usprawnienia mechanizmu ZGC oraz kolejne zmiany rozwijające nowoczesny model programowania współbieżnego.

Wirtualne wątki (Virtual Threads)

Jest to jedna z największych zmian w modelu współbieżności od wielu lat.

Do tej pory każdy utworzony wątek (Thread) był mapowany na wątek systemu operacyjnego. Chociaż takie rozwiązanie jest wydajne przy niewielkiej liczbie zadań, to przy tysiącach jednoczesnych operacji prowadzi do dużego zużycia pamięci oraz ograniczeń wynikających z liczby dostępnych wątków systemowych.

Virtual Threads działają inaczej. Są lekkimi wątkami zarządzanymi przez JVM, która mapuje je na niewielką liczbę klasycznych wątków systemowych. Dzięki temu aplikacja może wykonywać setki tysięcy współbieżnych zadań bez konieczności utrzymywania takiej samej liczby wątków systemowych.

Największą zaletą tego rozwiązania jest możliwość pisania prostego, blokującego kodu bez rezygnowania z wysokiej wydajności. Zamiast budować skomplikowane łańcuchy operacji asynchronicznych z wykorzystaniem CompletableFuture czy reaktywnych frameworków, w wielu przypadkach można pozostać przy klasycznym modelu programowania.

Klasyczna pula wątków:

try (var executor = Executors.newFixedThreadPool(100)) {
    for (int i = 0; i < 10_000; i++) {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return null;
        });
    }
}

To samo z wykorzystaniem Virtual Threads:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 10_000; i++) {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return null;
        });
    }
}

Kod praktycznie się nie zmienia, jednak w drugim przypadku każde zadanie wykonywane jest we własnym Virtual Thread. Dzięki temu JVM może efektywnie zarządzać ogromną liczbą współbieżnych operacji przy znacznie mniejszym zużyciu pamięci.

Record Patterns

Ich głównym celem jest uproszczenie pracy z rekordami poprzez jednoczesne sprawdzenie typu obiektu oraz rozpakowanie jego pól. Dzięki temu kod staje się krótszy, bardziej czytelny i eliminuje konieczność ręcznego wywoływania metod dostępowych.

Przykładowy rekord:

record Person(String firstName, String lastName, int age) {}

Kod przed JDK 21:

Object object = new Person("Jan", "Kowalski", 30);

if (object instanceof Person person) {
    String firstName = person.firstName();
    String lastName = person.lastName();
    int age = person.age();

    System.out.println(firstName + " " + lastName + " (" + age + ")");
}

Kod po zmianach

Object object = new Person("Jan", "Kowalski", 30);

if (object instanceof Person(String firstName, String lastName, int age)) {
    System.out.println(firstName + " " + lastName + " (" + age + ")");
}

Zmiany w switch (Pattern Matching for switch)

Do tej pory, aby wykonać różne operacje w zależności od typu obiektu, najczęściej wykorzystywano długie instrukcje if-else wraz z operatorem instanceof. Przy większej liczbie typów taki kod szybko stawał się trudny do odczytania i utrzymania.

Dzięki Pattern Matching for switch całą logikę można przenieść do jednej instrukcji switch, co poprawia czytelność oraz ułatwia dalszy rozwój aplikacji.

Przed Java 21:

static String describe(Object object) {
    if (object instanceof Integer i) {
        return "Liczba całkowita: " + i;
    } else if (object instanceof Double d) {
        return "Liczba zmiennoprzecinkowa: " + d;
    } else if (object instanceof String s) {
        return "Tekst: " + s;
    } else {
        return "Nieznany typ";
    }
}

Java 21 – Pattern Matching for switch:

static String describe(Object object) {
    return switch (object) {
        case Integer i -> "Liczba całkowita: " + i;
        case Double d -> "Liczba zmiennoprzecinkowa: " + d;
        case String s -> "Tekst: " + s;
        case null -> "Brak wartości";
        default -> "Nieznany typ";
    };
}

Record Patterns pozwala pisać bardziej deklaratywny kod, który jest jednocześnie krótszy i łatwiejszy w utrzymaniu niż tradycyjne instrukcje if-else.

Kolekcje sekwencyjne (Sequenced Collections)

Do tej pory różne implementacje kolekcji oferowały odmienne metody dostępu do pierwszego i ostatniego elementu. Przykładowo LinkedList udostępniała metody getFirst() i getLast(), natomiast ArrayList już nie. Powodowało to konieczność pisania kodu zależnego od konkretnej implementacji kolekcji.

Java 21 rozwiązuje ten problem poprzez wprowadzenie nowych interfejsów:

SequencedCollection
SequencedSet
SequencedMap

Dzięki nim wszystkie kolekcje zachowujące kolejność elementów udostępniają ten sam zestaw metod, niezależnie od konkretnej implementacji.

Pozostałe zmiany

Oprócz opisanych wcześniej nowości Java 21 wprowadza również kilka usprawnieńw wersji Preview. String Templates, które upraszczają tworzenie i formatowanie ciągów znaków, Scoped Values stanowiące nowoczesną alternatywę dla ThreadLocal, a także kolejną wersję Foreign Function & Memory API, której celem jest docelowe zastąpienie mechanizmu JNI.

Na uwagę zasługuje również Generational ZGC, czyli nowa wersja kolektora Z Garbage Collector, wykorzystująca podział obiektów na młode i stare generacje. Dzięki temu możliwe jest osiągnięcie lepszej wydajności aplikacji oraz niższego narzutu związanego z odśmiecaniem pamięci.

Najważniejsze zmiany od Java 18 do Java 21

WersjaNajciekawsze nowościCzy warto znać?
Java 18UTF-8 jako domyślne kodowanie, Simple Web Server⭐⭐⭐☆☆
Java 19Virtual Threads (Preview), Record Patterns (Preview), Structured Concurrency⭐⭐⭐⭐⭐
Java 20Scoped Values (Incubator), rozwój Virtual Threads i Record Patterns⭐⭐⭐⭐☆
Java 21 (LTS)Virtual Threads, Pattern Matching for switch, Record Patterns, Sequenced Collections⭐⭐⭐⭐⭐

Podsumowanie

Java 21 to wydanie LTS, które skupia się przede wszystim na poprawie wydajności i lepszym zarządzaniu pamięcią. Dodano też drobne zmiany w samym języku, takie jak finalne Pattern Matching for switch oraz Record Patterns. Upraszczają one tworzenie nowoczesnych aplikacji i sprawiają, że kod staje się bardziej czytelny oraz łatwiejszy w utrzymaniu.

Jeżeli obecnie korzystasz z Java 17, migracja do Java 21 jest zdecydowanie warta rozważenia. Oprócz nowych funkcji zyskasz również liczne usprawnienia wydajności, bezpieczeństwa oraz kolejne lata wsparcia dla wersji LTS.

Dodaj komentarz