Neuroróżnorodność w miejscu pracy
19.09.20237 min
Adam Kukołowicz

Adam KukołowiczCo-founderBulldogjob

Java 21 wydana! Nowy LTS stawia na lekkie wątki

Sprawdź, jakie nowości i usprawnienia przynosi Java 21, która będzie wspierana jako LTS przez 5 lat.

Java 21 wydana! Nowy LTS stawia na lekkie wątki

Witamy Javę 21, która będzie aktywnie wspierana przez najbliższe 5 lat - jako, że jest to wersja Long Term Support. Dlatego warto się przyjrzeć jakie funkcje wprowadza. Jak zwykle przyjrzymy się wszystkim JEP (Java Enchancement Proposal) wprowadzonym w najnowszej wersji, a jest ich aż 15. W Javie 20 narzekałem, że wieje nudą i nic się nie dzieje, tym razem naprawdę jest co opisywać.

Oprócz od dawna oczekiwanych zmian, mamy sporo nowości oznaczonych jako preview (które jeszcze mogą się nieznacznie zmienić w przyszłości) oraz w inkubatorze (które mogą się znacznie zmienić, lub w ogóle nigdy nie trafić do stabilnej Javy). Zobaczmy co oferuje Java 21.

Dopasowywanie wzorców w switch i rekordach

Dużym projektem, realizowanym przez kilka poprzednich wydań Javy było dorzucenie składni dopasowywania wzorców do nowej składni switch. W skrócie dopasowywanie wzorca polega na testowaniu wyrażenia pod kątem posiadania pewnych cech. Brzmi to jak coś bardzo przydatnego w switch, który tradycyjnie radził sobie tylko z wartościami w paru podstawowych typach. Teraz możemy sprawdzać typ w wyrażeniu/instrukcji switch:

static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}


Pattern matching pozwala też na doprecyzowanie dopasowania, przez wykonanie wyrażenia zwracającego Boolean:

static void testNew(Object obj) {
    switch (obj) {
        case String s when s.length() == 1 -> ...
        case String s                      -> ...
        ...
    }
}


Oprócz tego, z ważniejszych ficzerów switacha, to można w nim obsłużyć przypadek, gdy wartość jest nullem, a jeżeli chodzi o resztę to warto zapoznać się z JEP 441.

Technika dopasowywania wzorców trafiła też do rekordów, gdzie służy do szybkiej dekonstrukcji wartości w nich zawartych, co definiuje JEP 440:

static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}


Nie musimy już wprost definiować zmiennych, do których wprost przypiszemy wartości z rekordu. Ta składnia to ogarnia. Dodatkowo jak najbardziej można to zagnieżdżać, wyciągając wartości z głębi rekordu.

Lekka wątkowość

Współbieżność w Javie jest zarówno błogosławieństwem, jak i przekleństwem. Historycznie wątki w Javie były też wątkami systemowymi, przez co były dość ciężkie. To trochę już nie pasuje do współczesnego podejścia, a rozwiązania takie jak w Go czy Kotlinie są coraz modniejsze. Twórcy Javy stwierdzili, że warto z tym zrobić porządek i zaproponowali wirtualne wątki. Wirtualne wątki nadal są uruchamiane na wątkach systemowych, ale nie są z nimi bezpośrednio związane, co umożliwia uruchomienie znacznie większej liczby wirtualnych wątków i przełączanie między nimi bez tworzenia takiej samej liczby wątką w systemie. API wątków zostało zmienione by odwzorować nowości, między innymi przez nową składnię do tworzenia wątków -  Thread.ofVirtual() dla wirtualnych, Thread.ofPlatform() dla tradycyjnych. Wątek wirtualny można stworzyć np. Tak:

Thread thread = Thread.ofVirtual().name("duke").unstarted(runnable)


Więcej informacji znajdziecie w JEP 444.

Kolekcje uporządkowane

Twórcy Javy zorientowali się, że język nie ma typu, który by oznaczał kolekcję uporządkowaną. Dlatego dziwnie to wygląda, gdy List, gdzie kolejność jest ważna, jest podklasą Collection, która o kolejność nie dba. Dlatego Java wprowadza 3 nowe interfejsy - SequencedSet, SequencedCollection i SequencedMap, żeby zrobić z tym porządek. W całość będą się one wpisywać właśnie tak:

Zapowiedź dalszych zmian - nowości z inkubatora i preview.

String Templates

W 2023 roku ludzie z projektu Javy zorientowali się, że praktycznie wszystkie współczesne języki (a nawet kilka tych przykurzonych) mają składnię do interpolowania ciągów znaków:

C#             $"{x} plus {y} equals {x + y}"
Visual Basic   $"{x} plus {y} equals {x + y}"
Python         f"{x} plus {y} equals {x + y}"
Scala          s"$x plus $y equals ${x + y}"
Groovy         "$x plus $y equals ${x + y}"
Kotlin         "$x plus $y equals ${x + y}"
JavaScript     `${x} plus ${y} equals ${x + y}`
Ruby           "#{x} plus #{y} equals #{x + y}"
Swift          "\(x) plus \(y) equals \(x + y)"


Natomiast obawiają się nowej funkcji, bo może być niebezpieczna i posłużyć do nowych ataków na systemy. Dlatego proponują nowe wyrażnie - nazwane wyrażeniem szablonowym (template expression), które będzie wyglądać tak:

String name = "Joan";
String info = STR."My name is \{name}";


Wygląda może trochę gorzej niż u konkurencji, ale za to STR to procesor szablonów, który będzie można podmieniać na inne. Oznacza to, że będzie można interpolować nie tylko ciągi znaków, ale to co sobie wymarzycie, dzięki udostępnieniu API do tworzenia tych procesorów. To nowość preview, opis w JEP 430.


Nienazwane zmienne, klasy

Pewnie znacie z wielu języków składnię, dzięki której można olać zmienną przez przypisanie jej do _? Taka sama funkcja jest w preview Javy 21 (JEP 443). Można tego użyć przy dekonstrukcji rekordu dopasowaniem do wzorca, albo pomijaniu zmiennych, które nigdy nie zostaną użyte. O przykłady jednego i drugiego:

r instanceof ColoredPoint(Point(int x, int _), Color _)

for (int i = 0, _ = sideEffect(); i < 10; i++) { ... i ... }


Jeżeli chodzi o nienazwane klasy i uproszczone metody, to jest to nowość w preview skierowana do początkujących w Javie (JEP 445). W skrócie chodzi o to, żeby w prostych programach nie trzeba było pisać:

public class HelloWorld { 
    public static void main(String[] args) { 
        System.out.println("Hello, World!");
    }
}


Bo jest to totalnie zbędne i może odstraszyć przyszłych adeptów programowania w Javie, a zamiast tego napisać:

void main() {
    System.out.println("Hello, World!");
}


Lepiej, prawda? Dla programistów Javy to bez znaczenia, ale czy sądzicie, że pomoże to językowi zdobyć nowych sympatków?


Przydatne do współbieżności

Z inkubatora trafia do preview funkcja Scoped Values. Ten ficzer opisywałem przy okazji Javy 20, natomiast wywołuję ją przy okazji współbieżności, bo sam JEP 446 potwierdza, że to podejście jest przydane do dzielenie się wartościami w wątkach wirtualnych. Scoped Values to wartości, które mogą zostać przekazame do wykorzystania w metodach, bez podawania ich jako parametry metod. Działają lepiej niż thread-local variables, przede wszystkim, że są zapisywane tylko raz i są dostępne tylko przez określony czas w czasie wykonywania. Bonusem jest to, że odczytanie takiej wartości jest równie szybkie jak odczyt lokalnej zmiennej.

Druga przydatna funkcja preview to ustrukturyzowana współbiezność. API StructuredTaskScope umożliwia stworzenie zadania, które jest zbiorem wspołbieżnych “podzadań” i skoordynowanie ich jako jednej jednostki. “Podzadanie” jest wykonywane we własnym wątku, a wynik zbierany jest do kupy w zadaniu rodzicu. Oto przykład z JEP 453:

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier<String>  user  = scope.fork(() -> findUser());
        Supplier<Integer> order = scope.fork(() -> fetchOrder());

        scope.join()            // Join both subtasks
             .throwIfFailed();  // ... and propagate errors

        // Here, both subtasks have succeeded, so compose their results
        return new Response(user.get(), order.get());
    }
}

Inne nowości

Odśmiecacz ZGC został poprawiony. ZGC to garbage collector, zaprojektowany z myślą o niskim opóźnieniu i wydajności. Teraz został zmodyfikowany by odśmiecanie w nim zachodziło pokoleniowo, co jest lepsze dla większości zastosowań. Po więcej zajrzyj do JEP 439.

Jeżeli siedzi w kryprografii to może Cię ucieszyć nowe API Key Encapsulation Mechanism, które służy do bezpieczniejszego przechowywania kluczy symetrycznych. Więcej w JEP 452.

API obcych funkcji i pamięci nadal jest w fazie preview (JEP 442) - służy ono do obcowania z programami innymi niż JVM-owe. W inkubatorze po raz 6 pojawia się API do wektorów (JEP 448).

Port JDK dla 32-bitowego Windowsa został oznaczony jako do wywalenia, dzięki JEP 449. Natomiast dynamiczne ładowanie agentów (java.lang.instrument) będzie od tej pory rzucać ostrzeżeniami. W przyszłości taka opcja zostanie wyłączona, o czym mówi JEP 451.

Co dalej z Javą?

Nowy LTS wprowadza solidny pakiet zmian. Zmiany składniowe uwspółcześniają język, jednak nie są wielką rewolucją, a raczej kontynuacją tego, co wprowadzono w poprzednich wydaniach. Interesującą zmianą są wątki wirtualne. Wydaje się, że Java stawia na nowy, lżejszy model współbieżności. Ciekaw jestem czy społeczności Javy go zaakceptuje i jak go zacznie wykorzystywać.

Skoro już jesteśmy przy akceptowaniu nowości, to spojrzałem na raport NewRelic, który pokazuje wykorzystanie Javy w styczniu 2023 na instancjach, które monitorują. 

Okazuje się, że większość projektów nadal śmiga na Javie 11, wydanej w 2018 roku, czyli 5 lat temu. Java 17, wydana 2 lata temu, dopiero zaczyna zyskiwać na popularności. Jak widać nie ma lekko w świecie Javy. Pocieszające jest to, że update’y na kolejne wersje powinny być coraz łatwiejsze i być może doczekamy momentu, w którym w momencie pojawienia się świeżego LTS-a, to poprzednia wersja będzie tą najbardziej popularną.

Na razie, pomimo półrocznego cyklu wydań i wprowadzeniu kolejnych nowości, ekosystem Javy jest jednym z najmniej ekscytujących na rynku.

<p>Loading...</p>