Java 17: Co nowego?

Przegląd nowości w Javie 17 będącej kolejną wersją Long Term Support (LTS) po Javie 11. Nowy sposób wydawania LTS i nowa licencja Oracle JDK

Java 17: Co nowego?

Nowe wydanie LTS

14 września 2021r. została wydana Java 17. Jest to nowe wydanie Long Term Support (w skrócie LTS). LTS to wersja z długim wsparciem co oznacza, że będzie otrzymywała aktualizacje przez minimum 8 lat.

Do czasu wydania Javy 17 nowe wydania nie będące LTS były publikowane co 6 miesięcy, natomiast nowe wersje LTS pojawiały się co 3 lata.

Po wydaniu Javy 17 firma Oracle ogłosiła, że planuje wydawać wersje LTS szybciej, czyli co 2 lata. Decyzja ta musi być jeszcze zaakceptowana. W tej chwili plan wydawania wersji LTS wygląda następująco:

  • Java 7 - wydana w lipcu 2011
  • Java 8 - wydana w marcu 2014
  • Java 11 - wydana we wrześniu 2018
  • Java 17 - wydana we wrześniu 2021
  • Java 21 - planowane wydanie we wrześniu 2023

Plan wspierania poszczególnych wersji Javy można sprawdzić poniżej:

Oracle Java SE Support Roadmap

Nowa licencja Oracle JDK

Java 17 jest dostępna na nowej licencji Oracle No-Fee Terms and Conditions License (w skrócie NFTC). Kolejne wersje Javy również będą wydawane na tej licencji. Licencja NFTC pozwala na użycie komercyjne i produkcyjne dystrybucji Oracle JDK za darmo.

Aktualizacje dla obecnej wersji LTS będą dostarczane aż do roku po wydaniu nowego LTS. Wersje nie będące LTS (np. Java 18) będą na licencji NFTC przez 6 miesięcy. Po darmowym okresie licencji NFTC Oracle będzie zmieniało licencję dla danej wersji na Oracle Technology Network License (w skrócie OTN).

Przypomnę, że we wrześniu 2017r. Oracle JDK 8 i 11 dotknęła zmiana jaką była licencja OTN. Licencja ta pozwalała używać Oracle JDK komercyjnie i na produkcyjnym środowisku tylko za opłatą subskrybcji (do użytku osobistego były darmowe). Po tej zmianie wielu użytkowników zaczęło przechodzić na uwcześnie darmowe dystrybucje JDK m.in. OpenJDK, AdoptOpenJDK czy Azul Zulu OpenJDK.

Decyzja ta ma odwrócić trend przechodzenia na inne dystrybucje. Pytanie czy nie jest już na to za późno? Więcej na temat licencji możesz przeczytać tutaj:

Oracle JDK License General FAQs

Nowa strona internetowa: dev.java

Wraz z Javą 17 została ogłoszona nowa strona internetowa:

Dev.java: The Destination for Java Developers

Można na niej m.in.:

  • uzyskać dostęp do tutoriali z Javy
  • przejść na stronę pobierania dystrybucji Oracle JDK
  • sprawdzić najnowsze wiadomości na temat Javy
  • sprawdzić w przeglądarce nowe funkcjonalności przychodzące wraz z najnowszą Javą (w trakcie pisania postu jeszcze nie dostępne)
Wkrótce nowe funkcjonalności Javy 17 będzie można przetestować w przeglądarce

Nowości

Java 17 dostarcza 14 zmian. Przyjęło się, że zmiany te są oznaczone skrótem JEP (JDK Enhancement Proposal) oraz liczbą porządkową. Poniżej krótki opis każdej z nich oraz link do strony, gdzie można znaleźć więcej informacji:

JDK 17

JEP 306: Restore Always-Strict Floating-Point Semantics

Java 1.2 wprowadziła dwa tryby: domyślny i restrykcyjny dla operacji zmiennoprzecinkowych. W domyślnym trybie operacje zmiennoprzecinkowe są zależne od platformy, na której są wykonywane. Oznacza to, że wynik może się minimalnie różnić na różnych platformach. Tryb restrykcyjny pozwala na operacje zmiennoprzecinkowe, które są niezależne od platformy, dzięki czemu wynik na każdej platformie będzie zawsze taki sam.

Tryb restrykcyjny można było włączyć używając słowa kluczowego strictfp na klasie, interfejsie lub metodzie (poza abstrakcyjnymi):

public strictfp class Calculator {}

public strictfp interface PlatonicSolid {}

public strictfp void sum() {}

Dzięki JEP 306 operacje zmiennoprzecinkowe są wykonywane zawsze w trybie restrykcyjnym tak jak było to przed Javą 1.2. Nie musimy już używać słowa kluczowego strictfp.

Celem tej zmiany było ułatwienie budowania bibliotek operujących na liczbach zmiennoprzecinkowych takich jak java.lang.Math czy java.lang.StrictMath.

JEP 356: Enhanced Pseudo-Random Number Generators

Wprowadza nowe interfejsy i implementacje dla pseudolosowych generatorów liczb (ang. Pseudorandom Number Generator, w skrócie PRNG). Usuwa również duplikacje kodu w istniejących klasach PRNG.

Pseudolosowe generatory liczb używają algorytmów obliczeniowych do generowania ogromnej sekwencji losowych wyników. W rzeczywistości, wszystkie wyniki zależą od wstępnych danych zwanych ziarnem (ang. seed). Jeżeli znamy ziarno i algorytm, możemy precyzyjnie określić całą sekwencję i wszystkie wartości będą łatwe do przewidzenia. Dlatego liczby nie są w pełni losowe, są pseudolosowe.

JEP-356 wprowadza m.in. główny interfejs java.util.random.RandomGenerator, który dostarcza jednolite API dla wszystkich istniejących (m.in. implementuje go teraz java.util.Random) i nowych PRNG. Dodaje także nową klasę java.util.random.RandomGeneratorFactory, która pozwala stworzyć generator z określonym algorytmem.

Dokumentacja interfejsu java.util.random.RandomGenerator

JEP 382: New MacOS Rendering Pipeline

Zmiana dla systemu operacyjnego MacOS. Wprowadza nowy potok renderujący (ang. rendering pipeline) w Java 2D API. Potok ten używa Apple Metal API. Jest to alternatywa dla zdeprecjonowanego (czyli przestarzałego) Apple OpenGL API. Zmiana ma na celu przygotowanie się do usunięcia Apple OpenGL API w przyszłych wersjach MacOS.

Potok renderujący określa kroki jakie system musi wykonać aby wyświetlić obiekt na ekranie dwuwymiarowym (2D). Większość graficznych aplikacji w Javie jest napisanych z użyciem biblioteki Swing, która renderuje obiekty przy pomocy Java 2D API.

JEP 391: MacOS/AArch64 Port

Zmiana ta portuje JDK na platformę MacOS/AArch64. Port ten umożliwi aplikacjom Java na uruchomienie na nowych komputerach Mac z układem Apple Silicon bazujących na 64-bitowej architekturze Arm.

Port dla Linux/AArch64 (JEP 237, Java 9) został już wykonany. Port dla Windows/AArch64 (JEP 388, Java 16) jest już prawie ukończony.

JEP 398: Deprecate the Applet API for Removal

API dla Appletów zostało zdeprecjonowane w JEP 289 (Java 9), ale nie zostało oznaczone do usunięcia. Ta zmiana oznacza Applet API do usunięcia, czyli w kolejnych wersjach Javy może zniknąć na zawsze.

Wszyscy dostawcy przeglądarek internetowych albo usunęli wsparcie dla pluginu Javy albo ogłosili plany by to zrobić. Applet bez niego nie jest w stanie działać w przeglądarce dlatego API przechodzi do przeszłości.

Klasa java.applet.Applet oznaczona do usunięcia

JEP 403: Strongly Encapsulate JDK Internals

Jest to kontynuacja JEP 396. Zmiana z zakresu zwiększania bezpieczeństwa aplikacji Java. Nie będzie już możliwe rozluźnianie silnej enkapsulacji wewnętrznych elementów poprzez jedną flagę w wierszu poleceń (--illegal-access). Dzięki tej fladze można było uzyskać dostęp do wewnętrznych elementów JDK poprzez refleksję od Javy 9 do Javy 16.

Pełna lista pakietów, do których można było uzyskać dostęp znajduje się tutaj.

JEP 406: Pattern Matching for switch (Preview)

Status Podglądu (ang. Preview) otrzymują kompletne lub prawie kompletne funkcjonalności języka Java. Można to określić beta testami, gdzie mile widziane jest wypróbowanie funkcjonalności i podzielenie się opinią na jej temat. Odradzane jest jednak używanie funkcjonalności w statusie Preview na produkcji.

W Javie 16 (JEP 394) operator instanceof został rozszerzony o tzw. dopasowanie wzorca (ang. pattern matching). Dopasowanie wzorca pozwala wyrazić powszechną logikę programu w sposób zwięzły i bezpieczny. W kodzie wygląda to następująco:

Object someObject = "javowiec.pl";

// Przed JEP 394
if (someObject instanceof String) {
    String someString = (String) someObject;
    String substring = someString.substring(9);
}

// Po JEP 394
if (someObject instanceof String someString) {
    String substring = someString.substring(9);
}

Często po sprawdzeniu czy jakiś obiekt jest instancją danej klasy castujemy jego typ na sprawdzany i definiujemy nową zmienną do której ją przypisujemy. Wzorzec ten można teraz osiągnąć jednym prostym zapisem: someObject instanceof String someString, który:

  • sprawdza czy obiekt someObject jest instancją klasy String
  • castuje obiekt someObject na typ String
  • przypisuje obiekt typu String do nowej zmiennej someString

JEP 406 skupia się na operacji switch, gdzie często porównujemy obiekt z wieloma alternatywami. Operator switch jest ograniczony i np. poniższego kodu nie można zapisać z jego użyciem:

// Przed JEP 406
static String formatter(Object someObject) {
	String formatted = "unknown";
	if (someObject instanceof Integer someInteger) {
		formatted = String.format("int %d", someInteger);
	} else if (someObject instanceof Long someLong) {
		formatted = String.format("long %d", someLong);
	} else if (someObject instanceof Double someDouble) {
		formatted = String.format("double %f", someDouble);
	} else if (someObject instanceof String someString) {
		formatted = String.format("String %s", someString);
	}
	return formatted;
}

Powyższy kod korzysta z dopasowania wzorca dla operatora instanceof, jednak nie jest idealny. Na szczęście zmienia się to dzięki JEP 406, gdzie wraz z wykorzystaniem wyrażeń switch (JEP 361) możemy zapisać:

// Po JEP 406
static String formatterPatternSwitch(Object someObject) {
	return switch (someObject) {
		case Integer someInteger -> String.format("int %d", someInteger);
		case Long someLong -> String.format("long %d", someLong);
		case Double someDouble -> String.format("double %f", someDouble);
		case String someString -> String.format("String %s", someString);
		default -> someObject.toString();
	};
}

Powyższy kod sprawdzi jakim typem jest zmienna someObject, wykona castowanie na odpowiedni typ, przypisze ją do zmiennej np. someInteger i wykona daną operację formatowania. Jeżeli żaden z typów nie będzie pasował, zostanie po prostu wywołana metoda toString() na zmiennej someObject. Na koniec switch zwróci wynik wykonanej operacji.

O zmianach dla operatora switch można napisać wiele. Dlatego na jego temat zostanie napisany oddzielny post. Obserwuj blog Javowiec.pl by tego nie przegapić 😋

JEP 407: Remove RMI Activation

Usuwa mechanizm Remote Method Invocation (RMI) Activation (pakiet java.rmi.activation), ale zostawia resztę składników RMI. Mechanizm ten został zdeprecjonowany i oznaczony do usunięcia w JEP 385 (Java 15). Powodem tej decyzji jest nieużywanie RMI Activation w systemach rozproszonych. Ponadto bardzo mało aplikacji Java używa RMI Activation obecnie, nowe natomiast już nie powstają.

RMI pozwala wywołać jednemu obiektowi metody drugiego obiektu znajdującego się na innej JVM. Pierwszy obiekt to Stub (reprezentuje zdalny obiekt i wysyła do niego żądania), zaś drugi to Skeleton (zdalny obiekt obsługujący żądania).

Mechanizm RMI Activation pozwalał serwisom bazującym na RMI wyeksportować Stuby, których ważność przekracza czas życia obiektu zdalnego (ang. remote object) lub JVM, na której obiekt zdalny się znajduje. W konwencjonalnym RMI Stub staje się nieważny, gdy tylko zdalny obiekt zostaje zniszczony. Wymaga to od klienta implementacji kompleksowej logiki obsługiwania błędów. Gdy klient wywoływał metodę na Stubie, którego można było aktywować, serwis RMI Activation tworzył instancję zdalnego obiektu na żądanie, dzięki czemu klient nie musiał przejmować się niepoprawnymi Stubami.

JEP 409: Sealed Classes

Zapieczętowane klasy (ang. Sealed Classes) i interfejsy były zaprezentowane w Javie 15 (JEP 360) jako Preview. Potrafią one ograniczać jakie klasy i interfejsy mogą je rozszerzać lub implementować.

Oznacza to, że autor np. interfejsu Shape (kształt) może wskazać, że jedynymi wspieranymi implementacjami będą Circle (koło) i Square (kwadrat). Triangle (trójkąt) nie będzie wspierany i nie będzie możliwości by ta klasa implementowała zapieczętowany interfejs Shape.

Zapieczętować klasę lub interfejs można słowem kluczowym sealed. Z kolei słowem kluczowym permits można określić jakie klasy bądź interfejsy będą rozszerzać lub implementować daną zapieczętowaną klasę/interfejs:

sealed interface Shape permits Circle, Square {}

final class Circle implements Shape {}

final class Square implements Shape {}

// Błąd kompilacji
// Triangle is not allowed in the sealed hierarchy
class Triangle implements Shape {}

O klasach zapieczętowanych można napisać dużo więcej. W związku z tym na jego temat również zostanie napisany oddzielny post.

JEP 410: Remove the Experimental AOT and JIT Compiler

Usuwa eksperymentalny kompilator Ahead-Of-Time (w skrócie AOT, komenda jaotc) oraz używany przez niego kompilator Graal będący kompilatorem Just-In-Time (JIT).

AOT pojawił się w Javie 9 (JEP 295), zaś Graal w Javie 10 (JEP 317). Kompilatory nie znalazły dużego zainteresowania, ale wymagały dużego wysiłku by je utrzymywać, dlatego zdecydowano się na ich usunięcie.

JEP 411: Deprecate the Security Manager for Removal

Security Manager obecny od Javy 1.0 został oznaczony do usunięcia. Od wielu lat nie był używany do zabezpieczania kodu Javy po stronie klienta. Po stronie serwera był z kolei rzadko używany.

Dzięki Security Managerowi m.in. Aplety mogły być uruchamiane w piaskownicy (ang. sandbox), która zakazywała dostępu do takich zasobów jak pliki czy sieć. Security Manager rysował linię pomiędzy niezaufanym kodem (aplety ze zdalnej maszyny) a zaufanym kodem (klasy na lokalnej maszynie). Przykładowo zaufany kod dostawał dostęp do zasobów, natomiast dostęp dla niezaufanego kodu był blokowany.

Klasa java.lang.SecurityManager oznaczona do usunięcia

JEP 412: Foreign Function & Memory API (Incubator)

Funkcjonalność znajdująca się w Inkubatorze to inaczej rozwijające się ekperymentalne API lub narzędzie przyjmujące formę oddzielnych modułów z przedrostkiem jdk.incubator. W przyszłości może znaleźć się w którejś wersji Javy lub nie.

Funkcjonalnością taką jest Foreign Function & Memory (FFM) API, dzięki któremu programy Java mogą pracować z kodem i danymi znajdującymi się poza środowiskiem uruchomieniowym Javy. Oznacza to, że program Java może wywoływać obce funkcje (np. kod spoza JVM) i bezpiecznie uzyskiwać dostęp do obcej pamięci (np. pamięć nie zarządzana przez JVM). API umożliwia używanie natywnych bibliotek i przetwarzanie natywnych danych bez niebezpieczeńśtw JNI (Java Native Interface). Celem FFM API jest zastąpienie JNI w przyszłości.

W tej chwili FFM API może:

  • Alokować obcą pamięć (jdk.incubator.foreign.MemorySegment , jdk.incubator.foreign.MemoryAddress i jdk.incubator.foreign.SegmentAllocator)
  • Manipulować i otrzymywać dostęp do obcej pamięci (jdk.incubator.foreign.MemoryLayout, jdk.incubator.foreign.MemoryHandles i jdk.incubator.foreign.MemoryAccess)
  • Zarządzać cyklem życia obcych zasobów (jdk.incubator.foreign.ResourceScope)
  • Wywoływać obce funkcje (jdk.incubator.foreign.SymbolLookup i jdk.incubator.foreign.CLinker)

FFM API znajduje i rozwija się w inkubatorze od Javy 14 (JEP 370). Początkowo nazywał się Foreign-Memory Access API i miał dawać dostęp do pamięci poza JVM.

JEP 414: Vector API (Second Incubator)

Kolejne API znajdujące się w Inkubatorze, w tym przypadku po raz drugi. Vector API zostało zaproponowane w JEP 338 (Java 16).

API te umożliwia wykonywanie operacji na wektorach na każdej platformie. Po ich wykonaniu są one kompilowane na wektorowe instrukcje sprzętowe.

Obecnie wektor jest reprezentowany przez abstrakcyjną klasę jdk.incubator.vector.Vector<E>. Wspierane typy elementów <E> to Byte, Short, Integer, Long, Float i Double. Natomiast wspierane wielkości wektora to 64, 128, 256, 512 i maksymalna liczba bitów na danej architekturze.

W przyszłości jest planowane ładowanie i przechowywanie wektorów z użyciem Foreign Function & Memory API (po wyjściu tego API z inkubatora).

JEP 415: Context-Specific Deserialization Filters

W Javie 9 zostały dostarczone deserializacyjne filtry (ang. deserialization filters) (JEP 290), które pozwalały aplikacjom i bibliotekom sprawdzać poprawność przychodzących i zserializowanych strumieni (ang. serialized streams) zanim się je zdeserializuje. Fitry te poprawiają bezpieczeństwo, ponieważ deserializacja danych z nieznanego źródła jest z natury niebezpieczna.

Klient może zaimplementować interfejs filtra java.io.ObjectInputFilter a następnie ustawić go w java.io.ObjectInputStream. Metody interfejsu filtra będą wywoływane zanim obiekt zostanie zinstancjonowany i zdeserializowany by sprawdzić jego poprawność. Filtr może zwrócić status ALLOWED (zaakceptowany), REJECTED (odrzucony) lub UNDECIDED (niezdecydowany).

W JEP 290 został również wprowadzony statyczny filtr, który można ustawić na całe JVM. Robi się to przy pomocy systemowej właściwości (ang. system property) jdk.serialFilter lub w pliku konfiguracyjnym conf/security/java.properties. Jeżeli właściwości są ustawione na obydwa sposoby, pierwszeństwo ma systemowa właściwość, która nadpisuje ustawienie w pliku konfiguracyjnym.

Plik konfiguracyjny conf/security/java.properties i właściwość jdk.serialFilter

Poniżej przykład tworzenia filtra z szablonu (ang. pattern), który zezwala na deserializowanie klas z pakietu pl.javowiec i odrzuca wszystkie inne:

var filter = ObjectInputFilter.Config.createFilter("pl.javowiec.*;!*");

Zmiana JEP 415 poprawia działanie tej funkcjonalności poprzez wprowadzenie fabryki filtrów (ang. filter factory). Fabryka może zostać użyta do aplikowania różnych filtrów w zależności od strumienia obiektu. Dzięki temu filtry są dynamiczne i zależne od kontekstu w przeciwieństwie do pojedynczego statycznego filtra ustawianego na całe JVM dla wszystkich strumieni.

Jeżeli fabryka filtrów nie zostanie ustawiona, wbudowana fabryka zwróci wspomniany statyczny filtr o ile został wcześniej ustawiony. Fabrykę filtrów ustawiamy podobnie jak statyczny filtr: na całe JVM. Natomiast właściwość jest inna: jdk.serialFilterFactory.

Plik konfiguracyjny conf/security/java.properties i właściwość jdk.serialFilterFactory

Podsumowanie

Java 17 została wydana 14 września 2021r. Jest to kolejna wersja z długim wsparciem (LTS) po Javie 11 wydanej 3 lata temu. Firma Oracle ogłosiła, że od teraz planowane jest szybsze wydawanie kolejnych wersji LTS, czyli co 2 lata. Oznacza to, że Java 21 będąca następną wersją LTS powinna być wydana już we wrześniu 2023r.

Java 17 posiada również nową licencję Oracle No-Fee Terms and Conditions License (NFTC), która pozwala na komercyjne i produkcyjne użycie dystrybucji Oracle JDK za darmo. Kolejne wersje Javy również będą wydawane na tej licencji a aktualizacje dla obecnej wersji LTS będą dostarczane aż do roku po wydaniu nowego LTS. W przypadku komercyjnego i produkcyjnego użycia po darmowym okresie licencji NFTC będzie można wykupić subskrypcję lub przejść na kolejną wersję LTS.

Oracle zaprezentowało nową stronę dev.java, która ma być głównym źródłem wiedzy dla Java Developerów. Ciekawie zapowiada się możliwość sprawdzenia w przeglądarce nowych funkcjonalności przychodzących wraz z najnowszym wydaniem Javy.

Java 17 dostarcza 14 zmian, z czego najciekawiej prezentują się zapieczentowane klasy i interfejsy, dopasowanie wzorca dla operatora switch oraz usprawnienia w deserializacyjnych filtrach poprzez dodanie fabryki filtrów. Sentymentalne jest oznaczenie do usunięcia Apletów, które wkrótce staną się jedynie melodią przeszłości.

Załóż konto na Javowiec.pl i automatycznie zapisz się na newsletter by nie przeoczyć kolejnej dawki technicznej wiedzy dla Programisty Java! 😉

Liczba komentarzy:

Zaloguj się lub dołącz do społeczności Javowców, aby móc uczestniczyć w dyskusji 🙂