Java 17 – nowa wersja z rozszerzonym wsparciem (LTS)

14 września 2021 dostaliśmy długo wyczekiwaną wersję języka Java z długim wsparciem. Oznacza to, że będziemy dostawać aktualizacje przez 5 lat. Poprzednia wersja LTS, oznaczona numerkiem 11 wyszła 3 lata temu. W tym czasie pojawiło się kilka nowych funkcji, które ułatwiają tworzenie kodu w Javie. Zobaczmy, co nowego dodaje siedemnastka do tej grupy.

Klasy zapieczętowane

Dzięki klasom zapieczętowanym możemy określić, jakie klasy mogą dziedziczyć po danej klasie. Ma na to na celu danie autorom kodu na większą kontrolę nad kodem odpowiedzialnym za implementację funkcjonalności, ograniczenie możliwości używania klasy nadrzędnej oraz w przyszłości lepszą analizę czy pattern matching jest wyczerpujący.

package pl.mloza;
public sealed class Shape
permits Circle, Square, Rectangle {
}
final class Circle extends Shape {}
non-sealed class Square extends Shape {}
final class Rectangle extends Shape {}
view raw Shape.java hosted with ❤ by GitHub

Klasy, które dziedziczą po klasie zapieczętowanej, muszą określić czy są final, sealed (podając jakie klasy mogą po nich dziedziczyć) lub non-sealed (otwiera możliwość dziedziczenia).

Więcej: JEP-409.

Filtry deserializacji zależne od kontekstu

Deserializacja obiektów może być niebezpieczną czynnością. Często odtwarzamy obiekty ze strumienia danych, który jest poza naszą kontrolą. Odpowiednie spreparowanie danych w takim strumieniu może doprowadzić do wykonania niechcianego kodu. W Javie 9 został wprowadzony globalny filtr takich danych, który można było skonfigurować poprzez parametr systemowy, API lub parametry bezpieczeństwa. Niestety takie podejście okazało się niewystarczające. Zostało to zaadresowane w tym wydaniu. Filtry mogą być wybierane na podstawie kontekstu lub przypadku użycia. Na przykład jeśli używamy danej biblioteki do zdeserializowania danej grupy obiektów wtedy możemy zaaplikować odpowiedni filtr dla tych obiektów, możemy zdefiniować listę obiektów, które powinny się pojawić w strumieniu i odrzucić wszystkie inne.

Więcej: JEP-415.

Pattern matching for switch (preview)

Dopasowywanie wzorców pierwszy raz zobaczyliśmy w Javie 16. W Javie 14 pojawiła się nowa składnia switchy. W wersji 17 dostajemy dopasowywanie wzorców w switch. Dzięki temu będziemy mogli stworzyć np. taki kod:

Jest tutaj kilka elementów, na które powinniśmy zwrócić uwagę:

  • Po pierwsze: case null. Wcześniej jeśli do switcha przekazaliśmy nulla to dostawaliśmy NullPointerException. Nowa składnia pozwala nam na sprawdzenie tego casu w switchu,
  • Po drugie możemy dopasowywać i od razu rzutować na typ, czyli tak jak w Javie 16 i instanceof,
  • Po trzecie, możemy w warunku od razu użyć tzw. guarded statements, czyli wyrażeń, które zwracają true lub false i na ich podstawie decydować czy zmienna pasuje do wzorca.

Więcej: JEP-406.

Przywrócenie Always-Strict Floating-Point semantic

W Javie 1.2 wprowadzono rozróżnienie na default i strict floating point semantic. Stało się to przez to, że JVM miał problemy z interakcją z co-procesorem do obliczeń zmiennoprzecinkowych i jego zestawem instrukcji w architekturze x86. Dopasowanie dokładności operacji wymagało wielu dodatkowych operacji i było niewydajne. Wprowadzono więc wspomniane rozdzielenie. Tam, gdzie można było pozwolić sobie na niedokładność i zależność od sprzętu stosowano default floating point, tam, gdzie obliczenia musiały być niezależne od platformy, można było stosować słowo kluczowe strictfp, które zmieniało tryb i kosztem szybkości sprawiało, że obliczenia były niezależne od platformy.

Jednak od czasu wprowadzenia zestawu instrukcji SSE2 w Pentium 4 i późniejszych, które wspierają już bez problemu strictfp to rozdzielenie jest niepotrzebne.

Więcej: JEP-306.

Zmiany w generatorach liczb pseudolosowych

Pojawił się nowy interfejs: RandomGenerator, który unifikuje API dla wszystkich istniejących i nowych generatorów. Dostarcza on metody takie jak ints, longs, doubles, nextBoolean, nextInt, nextLong, nextFloat ze wszystkimi wariacjami parametrów jak dotychczas. Dodatkowo pojawiły się nowe wyspecjalizowane interfejsy:

  • SplittableRandomGenerator — posiada metody split oraz splits pozwalające na stworzenie nowego RandomGeneratora z już istniejącego, który będzie statystycznie niezależny,
  • JumpableRandomGenerator — dostarcza metody jump i jumps. Pozwalają one na przeskoczenie umiarkowanej liczby losowań kolejnych losowań,
  • LeapableRandomGenerator — dostarcza metody leap i leaps, które pozwalają na przeskoczenie dużej liczby kolejnych losowań,
  • ArbitrarilyJumpableRandomGenerator — dostarcza alternatywnych wersji metod leap i leaps pozwalających na przeskok ustalonej liczby losowań.

Ponadto możemy teraz rejestrować swoje implementacje algorytmów generujących losowe liczby w aplikacji, lub skorzystać z kilku nowych implementacji dostarczonych z nowym wydaniem. Poprawiono też wsparcie do streamów wprowadzając strumień liczb pseudolosowych. Wyeliminowano duplikacje kodu w istniejących algorytmach. Więcej: JEP-356.

Mocniejsza enkapsulacja wnętrzności Javy

Poza krytycznymi interfejsami, takimi jak sun.misc.Unsafe, dostęp do wnętrzności Javy nie będzie możliwy. Od wersji 9 do 16, można było rozluźnić te ograniczenia poprzez użycie flagi w linii poleceń, od nowego wydania nie będzie to już możliwe. Ma to na celu zwiększenie bezpieczeństwa i ułatwienie utrzymania JDK, oraz zachęcenie developerów do korzystania ze standardowych API. Więcej: JEP-403.

Usprawnienia dla użytkowników MacOS

Dodano wsparcie dla AArch64/MacOS, czyli będzie można uruchomić JDK na nowych procesorach Apple Silicon. Wcześniej też można było to zrobić poprzez wbudowany emulator Rosetta 2, jednak było to kosztem wydajności. Porty na wspomnianą architekturę istnieją już dla Linuxa, więc można było wykorzystać istniejący kod. Więcej: JEP-391.

Kolejnym usprawnieniem jest dodanie implementacji renderowania dla Apple Metal API, jako alternatywy dla istniejącego opartego na oznaczonym jako przestarzałe Apple OpenGL. Wsparcie dla tego ostatniego nie na razie nie zostanie usunięte. Więcej tutaj: JEP-382.

Foreign Function & Memory API (Inkubator)

Funkcjonalność ta jest rozwinięciem poprzednich, które pojawiły się w pierwszy raz w Javie 14 (Foreign-Memory Access API) oraz Javie 16 (Foreign Linker API).

Celem tej funkcjonalności jest wprowadzenie API, które pozwoli współpracować z kodem i danymi znajdującymi się poza JVM. Co prawda mamy już mechanizm JNI, który na to pozwala, jednak jest on trudny w użyciu i utrzymaniu, oraz wolny.

Więcej przeczytacie w oficjalnej dokumentacji: JEP-412.

Vector API (Second Incubator)

Normalnie procesor wykonuje jedną operację na jednym zestawie danych (SIMD – Single Instruction Single Data). Czyli przykładowo może dodać do siebie dwie liczby. Wektory pozwalają na wykonanie operacji na wielu liczbach naraz (SIMD -Single Instruction Multipe Data). Wirtualna maszyna Javy już wcześniej posiadała optymalizację, która automatycznie potrafiła zmieniać kod, aby wykorzystać możliwości procesora operującego na wektorach. Jednak możliwości te są ograniczone i delikatne. Nowe API odda w ręce programistów większą kontrolę nad procesem, dzięki temu będziemy mogli wprowadzić takie optymalizacje sami, w większej ilości miejsc.

Dokumentacja podaje przykład pętli, którą możemy zoptymalizować. Pętla wygląda następująco:

void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}

Oraz wersja z wykorzystaniem API wektorowego.

static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i, m);
}
}
view raw VectorApi.java hosted with ❤ by GitHub

Więcej znajdziecie w oficjalnej dokumentacji: (JEP-414)

Co zostało usunięte

Nowe wydanie oprócz dodatkowych funkcjonalności pozbywa się też części już nieużywanych i niepotrzebnych. Dzięki temu staje się ono łatwiejsze w utrzymaniu i rozwoju. Co zostało usunięte lub oznaczone do usunięcia w kolejnych wersjach:

  • Applet API — większość przeglądarek już nie wspiera appletów Javy lub przestanie je wspierać w najbliższej przyszłości (JEP-398)
  • Usunięcie RMI Activation — zapowiedziane w Javie 15 usunięcie mechanizmu RMI Activation, jednak reszta RMI pozostanie nadal w Javie (JEP-407)
  • Usunięcie eksperymentalnych kompilatorów AOT i JIT — czas przeznaczony na utrzymanie tych dwóch kompilatorów był znaczący, jednocześnie bardzo mało osób z nich korzystało. (JEP-410)
  • Oznaczenie Security Managera jako do usunięcia — wykorzystywany był od Javy 1.0 do zabezpieczania kodu działającego po stornie klienta, rzadko do zabezpieczenia kodu serwerowego. Podjęta została decyzja o jego usunięciu wraz z Applet API. (JEP-411)

Podsumowanie

To już wszystko, co zobaczymy w nowej wersji języka Java i JVM. Lista zmian jest długa, jednak większość programistów zapewne zainteresują tylko klasy zapieczętowane. Może jeszcze użytkownicy MacOS z nowymi procesorami zauważą przyspieszenie 🙂 Reszta zmian będzie dla większości przeźroczysta, lub jest jeszcze w stanie inkubacji.

Z drugiej strony, wydanie LTS oznacza, że dużo firm umożliwi migrację z Javy 11, dzięki czemu w końcu będziemy mogli korzystać z wszystkich nowych rzeczy, które pojawiły się w poprzednich wersjach takich jak rekordy, bloki tekstu czy nowe algorytmy GC.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *