W dniu dzisiejszym głównym tematem jest „zamrożenie” listy JEPów w JDK 21. Oprócz tego jednak porozmawiamy też o Nullach w Valhalli. Na koniec zaś obowiązkowy Release Radar.
1. „Zamrożona” lista funkcjonalności JDK 21
Zacznijmy od największego ogłoszenia.
JDK 21 przeszło do fazy Rampdown. W teorii, etap ten powinien oznaczać zakończenie wprowadzania zmian, ale więcej na ten temat ujawnię później. Choć zdawałem sobie sprawę z ogromu OpenJDK 21, finalna lista JEP-ów dalej wywołuje moim zdaniem mnie wrażenie.
Z uwagi na liczbę nowych funkcji, nie mogę zagłębiać się w każdą z nich. Postanowiłem więc przeprowadzić mały eksperyment – zamierzam przedstawić każdą funkcję w jednym zdaniu oraz poprzez fragment kodu (dla stabilnych funkcjonalności).
Dlatego bez zbędnego przedłużania, zaczynamy, bo i tak dużo przed nami:
Stabilne
431: Sequenced Collections
W wypadku uporządkowanych kolekcji, wprowadzono spójny interfejs do pobierania pierwszego i ostatniego elementu, a także odwracania sekwencji.
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
439: Generational ZGC
Jako, że prawdopodobieństwo konieczności „sprzątania” przez GC jest zmniejsza się wraz z czasem życia danego obiektu, posiadanie innego pipeline dla krótko i długo żyjących obiektów jest standardem w GC, a teraz trafia też ZGC.
440: Record Patterns
Możliwość łatwej destrukturyzacji Rekordów, umożliwiająca wyciągnięcie z nich konkretnych pól, a także używanie w pattern matchingu (kolejny JEP).
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
441: Pattern Matching for switch
Możliwość użycia switch
do pattern matchingu, z uwzględnieniem zaawansowanych opcji.
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();
};
}
444: Virtual Threads
Wprowadzenie do JVM konceptu wątków zarządzanych nie przez system operacyjny, a przez samą maszynę wirtualną.
Thread.builder().virtual().factory();
449: Deprecate the Windows 32-bit x86 Port for Removal
Goodnight, Sweet Prince
451: Prepare to Disallow the Dynamic Loading of Agents
W celu zwiększenia bezpieczeństwa Javy, twórcy planują w przyszłości zabronić ładowania pewnych kategorii Agentów Maszyny Wirtualnej bez podania specjalnej flagi.
452: Key Encapsulation Mechanism API
Wprowadzenie do Javy standardowego API pozwalającego na użycie techniki znanej jako KEM, wykorzystywanej min. w algorytmach kryptografii kwantowej.
Preview
430: String Templates (Preview)
Możliwość tworzenia w Javie bloków tekstu, dających dużo większe możliwości i bezpieczeństwo niż interpolacja stringów.
JSONObject doc = JSON_VALIDATE."""
{
"name": \{name},
"phone": \{phone},
"address": \{address}
};
""";
445: Unnamed Classes and Instance Main Methods (Preview)
Zmiana Launch Protocol w Javie, umożliwiająca pisania mocno uproszczonych klas Java, głównie na potrzeby edukacji.
void main() {
System.out.println("Hello, World!");
}
442: Foreign Function & Memory API (Third Preview)
Wprowadzenie typowanej interoperacyjności z programami napisanymi w C (a w przyszłości też innych języków kompilowanych), a także natywnej pamięci systemu operacyjnego.
443: Unnamed Patterns and Variables (Preview)
Wprowadzenie do języka wildcard _
, używanego wtedy, gdy nie chcemy w pattern matchingu definiować konkretnej oczekiwanej wartości/typu, a także jako hint dla linterów, gdy wiemy że zadeklarowana zmienna jest niepotrzebna.
if(r instanceof Point(_, int y))
453: Structured Concurrency (Preview)
Zestaw struktur umożliwiających zarządzanie wątkami (nie tylko wirtualnymi), a zwłaszcza potencjalnymi błędami.
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() ->fetchOrder());
scope.join().throwIfFailed();
return new Response(user.get(), order.get());
}
446: Scoped Values (Preview)
Alternatywa dla ThreadLocala, zaprojektowana głównie z myślą o wirtualnych wątkach.
final static ScopedValue<...> V = ScopedValue.newInstance();
ScopedValue.where(V, <value>).run(() -> { V.get() });
Inkubacja
448: Vector API (Sixth Incubator)
Czekające na Valhalle API umożliwiające operacje wektorowe, udostępniane przez nowoczesne procesory.
A na koniec, jeśli Wam mało, mam dla Was dwa wideo.
Jedno z nich to świetne opracowanie ze strony kanału Inside Java, w którym Nicolai Parlog w 20 minutowym filmiku w szerszym stopniu opisuje każdy z JEP-ów:
Drugie to dla mnie okazja, aby przemycić jeden z moich ulubionych kanałów programistycznych na YouTube. Jeśli cenicie sobie lekką, „memiczną” formułę opracowań, to Fireship zdecydowanie powinien przypaść Wam do gustu. Zawartość tego kanału to przede wszystkim krótkie, poniżej pięciominutowe filmy.
Autor skupia się na najistotniejszych tematach ze świata programowania. Dla mnie zawsze jest to fantastyczny sposób na śledzenie nowości z obszarów poza moją główną specjalizacją, jak na przykład nowe języki programowania czy innowacje typu bazy wektorowe.
Podkreślam to, ponieważ niedawno na kanale Fireship ukazało się video poświęcone JDK 21. Zachęcam do sprawdzenia, czy taki format odpowiada też Wam. Moim zdaniem, warto dać mu szansę:
PS: Nie wiem czy pamiętacie, ale z tym „żadnych zmian” to różnie bywało w przeszłości. Przy okazji JDK 20 twórcy zapomnieli bowiem wrzucić do inkubacji Vector API, które zostało dodane w zasadzie zaraz przed premierą wydania.
PS2: Jeśli chodzi o Structured Concurrency, to za kulisami działo się masę ciekawych rzeczy i dyskusji, które myślałem opisać właśnie przy okazji oficjalnego opublikowania listy JEP-ów. Jak już wspomniałem, to okazuje się być jednak na tyle mocarne, że temat zostawimy sobie na później.
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Valhalla już oficjalnie zamiesza w podejściu do Nullowalności w Javie
Dla dynamiki, zmieniamy teraz tempo i zamiast skrótowo omówić nadchodzącą wersję, której premiera planowana jest na jesień, porozmawiamy szerzej o tym, co zostało zapowiedziane w kontekście Valhalli. Data premiery nie jest jeszcze znana, ale kolejne dokumenty sugerują, że zbliżamy się do finalnego wydania. Brian Goetz podzielił się Design document on nullability and value types, który podsumowuje nowy etap rozwoju projektu. Znów możemy spojrzeć na wyzwania, ale także na nowe możliwości, które się pojawiają. Valhalla wydaje się być projektem rozwijanym w sposób mocno empiryczny.
Jeśli nie śledzicie na bieżąco rozwoju Valhalli, możliwe, że umknął Wam fakt, że w pewnym momencie twórcy zaproponowali sufiksy .val
i .ref
. Miały one informować, czy chcemy używać danego obiektu jako wartości, czy jako referencji. To było dla mnie najbardziej problematyczne spośród proponowanych zmian, obawiałem się bowiem komplikacji składni. Na razie wygląda na to, że uda się z nich zrezygnować. Prace nad rozwojem całego projektu pozwoliły zredukować różnice między typami prymitywnymi a obiektami do dwóch fundamentalnych różnic – posiadania wartości domyślnej (jak 0 w przypadku int) oraz wsparcia dla nullowalności. W kolejnej iteracji prac nad Valhallą, pierwsze z nich zostało zaadresowane poprzez koncepcję implicit constructors
dla Value Class.
value class Complex {
private int re;
private int im;
public implicit Complex();
public Complex(int re, int im) { ... }
...
}
Co do nullowalności, pojawiła się propozycja wprowadzenia dwóch dodatkowych oznaczeń przy definiowaniu typu – !
oznaczający nie dopuszczamy nulla oraz ?
oznaczający ten obiekt jest nullowalny. W skrócie:
Foo?
oznacza ten typ zawiera w swoim zbiorze wartości nullFoo!
oznacza ten typ nie zawiera w swoim zbiorze wartości nullFoo
oznacza 🤷♂️ – inaczej mówiąc, nie określony stan nullowalności
Z czasem, twórcy będą dążyć do tego, aby wersja bez odpowiedniej adnotacji przyjmowała cechy wariantów anotowanych – na ten moment wydaje się, że nieoznaczone Foo
będzie w większości przypadków traktowane jako nienulowalne.
Niestety, weryfikacja powyższych będzie wymagała mieszaniny sprawdzeń na poziomie czasu kompilacji i czasu wykonania, będzie też bardzo trudna i na początku ograniczona – dodawanie nowych funkcji do istniejącego języka nigdy nie jest proste, a szczegółowe rozwiązania zaproponowane przez Briana pokazują, jak wiele przypadków trzeba obsłużyć. Na dowód tego, dzień po udostępnieniu Design document on nullability and value types, na listę mailingową trafiło Briefest summary of today’s Valhalla+nullness picture, będące destylatem dokumentu. Mimo że jest to bardzo dobre podsumowanie, aby naprawdę zrozumieć istotę zmian, musiałem przejrzeć oryginalny tekst. Mapa to jednak nie terytorium.
Zainstaluj teraz i czytaj tylko dobre teksty!
3. Release Radar
Guava 32.0
Guava czasy swojej największej chwały ma już chyba za sobą. Wiele z funkcjonalności, które przynosiła – w swoim czasie rewolucyjnych – zostało włączonych do JDK albo rozwinięte przez wyspecjalizowane projekty. Guava co prawda posiada jeszcze parę unikalnych trików, takich jak Multimap
i unikalne implementacje innych kolekcji czy struktur grafowych, a struktury pomocnicze do obsługi strumieni. To i inne dodatki sprawiają, że w dalszym ciągu jest ona ważnym graczem w ekosystemie, po prostu już nie tak „domyślnym”, jak było to na poziomie JDK 1.7. Projekt jest też wciąż rozwijany, a niedawno doczekał się wersji Guava 32.0
Ta zaś oznacza wielkie sprzątanie – adnotacja @Beta
została usunięta z niemal wszystkich klas, w praktyce oznaczając dużą stabilizację wielu API. Celem adnotacji @Beta
było umożliwienie użytkownikom przekazania opinii przed całkowitym ustabilizowaniem API, teraz twórcy postanowili trochę posprzątać projekt. Dlatego też nowe wydanie Guava traktować warto jako inwentaryzację ułatwiającą pracę na przyszłość i nie wprowadza jakichś dużych zmian. Postanowiłem jednak o niej wspomnieć, gdyż osoby które bały się niestabilnych API będą z czystym sumieniem mogły używać sporego zbioru ustabilizowanych metod. Warto też wspomnieć, że twórcom udało się zamknąć pewną wiszącą podatność, niestety kosztem pewnych problemów z obsługą plików na Windowsie i jeśli używamy tego systemu, powinniśmy przeskoczyć od razu do wersji 32.0.1.
Scala 3.3.0
Uwaga: Scala przeszła znaczące zmiany w swoim cyklu wydawniczym.
Scala 3.3.x to pierwsza seria wydań Long Term Support (LTS), którą aktywnie będzie utrzymywano przez co najmniej trzy lata. Analogicznie do modelu wydawniczego Javy, Scala będzie posiadała mniejsze wydania (3.4, 3.5, itd.), zwane Scala Next. Poprawki błędów i usprawnienia z tych wydań będą przenoszone z powrotem i wydawane jako poprawki 3.3.x, jednak LTS nie będzie zawierało zmian w API. Rozwiązanie to ma na celu zwiększenie wygody dla twórców bibliotek, którzy będą otrzymywać regularne poprawki błędów bez konieczności wymuszania na użytkownikach aktualizacji wersji kompilatora.
Sama Scala 3.3.0 wprowadza ponownie linting do kompilatora Scala, co umożliwia wykrywanie nieużywanych symboli i wartości, z zapowiedzią dodatkowych opcji w przyszłości. Wprowadzono również bardziej spójną składnię bez nawiasów klamrowych. Pozwala to zastąpić nawiasy wokół parametrów metod dwukropkiem, co prowadzi do czystszej, krótszej i często bardziej czytelnego syntanu.
Dodano również dwie nowe metody do standardowej biblioteki, boundary
i break
, które umożliwiają bezpieczniejsze i bardziej wyraziste zwracanie wartości z bloków. Pozwala to wskazać, że wyjście z funkcji może nastąpić również w ramach jednej z lambd, co twórcy nazywają „szybką ucieczką”.
def sumOfRoots(numbers: List[Double]): Option[Double] = boundary:
val roots = numbers.map: n =>
println(s" * calculating square root for $n*")
if n >= 0 then Math.sqrt(n) else break(None)
Some(roots.sum)
Hibernate Reactive 2.0
Na koniec chciałbym wspomnieć o premierze Hibernate Reactive 2.0. Na pierwszy rzut oka, wydaje się być to dosyć skromne wydanie – w dzienniku zmian widzimy jedynie kompatybilność z Hibernate ORM 6.2.4 oraz Vert.x SQL client 4.4. Niemniej jednak, nie dajcie się zwieść pozorom – przy bliższym przyjrzeniu się, okaże się, że jest to kolosalny skok.
Hibernate Reactive 1.0 był bowiem kompatybilny jedynie z linią Hibernate 5.x. Zmiany wprowadzone przez Hibernate 6.0, wydany około rok temu, a następnie rozwijane w kolejnych wydaniach, są na tyle znaczące, że twórcy zachęcają do zapoznania się z poradnikiem migracji.