Spis treści

Java 17 - co nowego?

14 września 2020 wydana została Java 17. Co nowego niesie ze sobą to wydanie? Jakie zmiany zostały ustabilizowane, a co wciąż jest ekperymentalne? Zapraszam na krótką wyprawę, na której chcę się przyjrzeć nowościom.

Odnośniki: API, Lang Spec i JVM Spec

Oto dwa najważniejsze odnośniki:

Warto przejrzeć New API since JDK 11 - podstronę w dokumentacji API - aby mieć przegląd zmian wprowadzonych w poprzednich wersjach.

Nowe wydanie Javy to idealny, wyczekany prezent (spóźniony o cztery dni, ale nie gniewam się) na moje urodziny. W końcu wydana została wersja Long Term Support - przez wielu developerów uznawana za stabilną.

Nowe wydanie

Na stronie JDK 17 znajduje się ostateczna lista JEP-ów, które wylądowały w nowym wydaniu.

O części z nich (np. o klasach “zapieczętowanych”) pisałam już we wpisach:

Jeśli ich jeszcze nie czytałaś/czytałeś, zapraszam! 😄

Lista zmian

Oto lista zmian w Javie 17 z odnośnikami do JEP-ów opisujących dokładniej daną zmianę. Nieco dalej przeczytasz o niektórych z tych zmian w dziennikarskim skrócie.

Zobaczmy, co kryje się pod nimi kryje.

Garść szczegółów

Pattern matching w switch (preview)

To najciekawsza zmiana, która wprowadza dalsze rozszerzenia dopasowywania wzorca w instrukcji/wyrażeniu switch:

  • w labelkach case mogą pojawić się wzorce
  • można użyć wzorca null
  • wzorce mogą być “guarded”, czyli “strzeżone” przez wyrażenie boolowskie bądź posiadać nawiasy (wspomagające parsowanie)

Zamiast kodu

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

będzie można napisać:

1
2
3
4
5
6
7
8
9
static String formatterPatternSwitch(Object o) {
    return switch (o) {
        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        -> o.toString();
    };
}

Pokrycie

Switch musi pokryć wszystkie możliwe przypadki, musi więc albo posiadać wariant default albo dotyczyć hierarchii klas sealed:

Ten kod nie będzie poprawny:

1
2
3
4
5
6
static int coverage(Object o) {
    return switch (o) {         // Error - incomplete
        case String s  -> s.length();
        case Integer i -> i;
    };
}

ale ten już tak:

1
2
3
4
5
6
7
static int coverage(Object o) {
    return switch (o) {
        case String s  -> s.length();
        case Integer i -> i;
        default -> 0;
    };
}

podobnie jak ten:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
sealed interface S permits A, B, C {}
final class A implements S {}
final class B implements S {}
record C(int i) implements S {}  // Implicitly final

static int testSealedCoverage(S s) {
    return switch (s) {
        case A a -> 1;
        case B b -> 2;
        case C c -> 3;
    };
}

Dopasowanie null-a

Jeśli w wyrażeniu/instrukcji switch pojawi się null, można się do niego “dopasować”:

1
2
3
4
5
6
7
8
static void test(Object o) {
    switch (o) {
        case null      ->  System.out.println("null");
        case String s  -> System.out.println("String: "+s);
        case Integer i -> System.out.println("Integer");
        default  -> System.out.println("default");
    }
}

Wzorce - strażniki

Można testować pewne właściwości dopasowanego wzorca, przy czym logika testu może być zawarta w labelce case:

1
2
3
4
5
6
7
static void test(Object o) {
    switch (o) {
        case String s && (s.length() == 1) -> ...
        case String s                      -> ...
        ...
    }
}

Tego jeszcze nie ma, niestety.

Ale może będzie wkrótce. To JEP 405:

1
2
3
4
5
6
7
8
9
int eval(Expr n) {
     return switch(n) {
         case IntExpr(int i) -> i;
         case NegExpr(Expr n) -> -eval(n);
         case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
         case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
         default -> throw new IllegalStateException();
     };
}

Odnośniki:

Nowy sposób renderowania Java 2D w systemie macOS

Zaimplementowano “potok renderujący” przy użyciu API Apple Metal, rezgnując z dotychczasowej implementacji wykorzystujacej przestarzałe API Apple OpenGL.

Może teraz, jak ktoś ma Maca, to mu się szybciej (i ładniej) wyrysuje na ekranie IntelliJ Idea albo Neatbeans?

Odnośnik:

JDK został przeportowany na macOS/AArch64

Apple zmienia CPU w swoich Mekintoszach z x86-64 na procesory swojego projektu oparte na architektrze ARM64. I potrzebuje na ten system portu macOS/AArch64.

Port na macOS będzie trzecim dla architektury AArch64 - istnieją już porty dla Linuksa i trwają prace nad portem dla Windowsa.

API apletów do kosza

Applet API - “deprecated” od wersji 9 - zostało oznaczone jako “deprecated for removal”. Dziś już chyba żadna przeglądarka nie wspiera apletów javowych.

Jak się chce robić coś, co widać w przeglądarce, to należy użyć JavaScript. Lub czegoś, co do JavaScriptu się kompiluje bądź transpiluje. Albo użyć WebGL.

Odnośniki:

Usunięcie RMI Activation

Mechanizm aktywacji zdalnych wywołań metod (RMI - Remote Method Invocation) został oznaczony do wyrzucenia w Javie 15, nikt nie zgłosił zastrzeżeń, więc teraz nastąpiło jego ostateczne zlikwidowanie.

Pakiet java.rmi.activation znika, podobnie jak dotycząca go część dokumentacji, biblioteki, zbiór testów regresyjnych oraz demon rmid wraz z dokumentacją.

Odnośniki:

Silne zamknięcie wewnętrznych pakietów JDK

Nie będzie można już użyć opcji --illegal-access aby włączyć dostęp do wewnętrznych elementów JDK.

Pakiety sun.misc oraz sun.reflect będą wyeksportowane w module jdk.unsupported i będą otwarte. Kod (np. zewnętrznych bibliotek) może wciąż mieć dotęp do ich niepublicznych elementów przez mechanizm refleksji.

Aby otworzyć dostęp do poszczególnych pakietów, wciąż można użyć opcji wiersza poleceń --add-opens albo atrybutu Add-Opens w manifeście pliku .jar.

Odnośniki:

Klasy sealed (klasy zapieczętowane)

Bardzo spodobało mi się tłumaczenie słowa “sealed” na język polski jako “zapieczętowane” (w artykule Adama Kukołowicza). Trochę długo się je wymawia, więc podejrzewam, że się nie przyjmie.

Implementacja w Javie 17 nie zmieniła się od wydania Javy 16, zmieniła jedynie status z preview feature na wersję ustabilizowaną.

Odnośniki:

Przywrócenie semantyki “always-strict” dla liczb zmiennoprzecinkowych

To zmiana głównie kosmetyczna, polegająca na zaktualizowaniu specyfikacji języka (z którego zostanie usunięte rozróżnienie między wyrażeniami strict i non-strict, niewielkiej zmianie w HostSpot (żeby nie używał specjalnego trybu przy operacjach zmiennoprzecinkowych) i dodaniu do javac nowgo ostrzeżenia (o niepotrzebnym użyciu moryfikatora strictfp).

W Javie 1.2 wprowadzono tryb non-strict ze względu na to, że zbiór instrukcji koprocesora x86 nie pozwalał na efektywne generowanie kodu maszynowego w pewnych przypadkach - wymaganie strictfp wiązało się z tym, że będzie generowana znaczna ilość dodatkowych instrukcji, ale za to obliczenia generujące underflow/overflow będą posiadały dokładnie takie same wyniki pośrednie na każdej platformie.

Jeśli dobrze zrozumiałam komentarz do tego JEP-a, to tryb domyślny, non-strict, stworzono właśnie po to, aby kosztem pewnych nieścisłości (względem specyfikacji IEEE 754) ten dodatkowy kod nie był generowany, szczególnie jeśli nie trzeba zapewniać “przenośności” kodu między różnymi architekturami.

Jednak rozszerzenia zbioru instrukcji we współczenych procesorach (SSE2 w procesorze Pentium 4 i późniejszych) zapewniły, że operacje zmiennoprzecinkowe typu strict są wspierane zupełnie naturalnie. Nie trzeba więc już utrzymywać specjalnego trybu non-strict “chroniącego” przed overheadem, bo overheadu już nie ma (mniej więcej od dwudziestu lat).

Odnośniki:

Rozszerzenie generatorów liczb pseudolosowych (PRNG)

Ta zmiana wprowadza nowe interfejsy i implementacje generatorów liczb pseudolosowych.

Głównym celem zmiany jest umożliwienie użycia różnych generatorów w jednej aplikacji oraz wsparcie programowania potokowego (stream-based).

Obecnie klasa Random (i jej dwie podklasy, ThreadLocalRandom oraz SecureRandom) nie może być używana w kontekstach, w których używana jest inna klasa, SplittableRandom (która dzięki metodzie split pozwala na wyforkowanie nowego generatora i przekazanie go do innego wątku).

Klasy te przed Javą 17 nie miały wspólnego interfejsu, więc po pierwsze, nie były wzajemnie “zastępowalne”, a po drugie nie można było użyć żadnej niezależnej implementacji generatorów w “przezroczysty” dla istniejącego kodu sposób.

W Javie 17 został wprowadzony interfejs RandomGenerator.

Implementują go klasy Random, SecureRandom, SplittableRandom, ThreadLocalRandom.

Została także dodana nowa klasa RandomGeneratorFactory<T extends RandomGenerator>. Pozwala ona na utworzenie niezależnych generatorów na postawie nazwy algorytmu bądź utworzenie generatora posiadającego określone właściwości.

Odnośniki:

Kontekstowe filtry deserializacyjne

Oracle wprowadza mechanizm, dzięki któremu deserializacja danych z niezaufanych źródeł zostanie lepiej zabezpieczona.

Strumień bajtów to wektor ataku, w którym ktoś - przez odpowiednio staranne przygotowanie danych wysyłanych do odbiorcy i deserializowanych na jego systemie - może doprowadzić nie tylko do utworzenia i zainicjowania obiektów, lecz także do takich wywołań, które zmienią stan obiektów aplikacji, obiektów bibliotek czy nawet samego JVM-a.

Przeciwdziałanie “atakom przy użyciu deserializacji” polega na użyciu filtrów deserializacyjnych: interfejs java.io.ObjectInputFilter pozwala na napisanie kodu sprawdzającego strumień do deserializacji.

Odnośniki:

Inne aspekty wydania

Kompatybilność

Sześciomiesięczny cykl wydań Javy to niesamowite tempo, zważywszy wszystkie ograniczenia, jakie funkcjonują w świecie oprogramowania pisanego w tym języku. Przede wszystkim chodzi o konieczność zachowania kompatybilności wstecznej.

To naprawdę silna strona tego języka. No tak, trochę tutaj namieszało wprowadzenie modułów w Javie 9 - niektórzy szacują, że ponad 50% kodu bibliotek dostępnych w Mavenie nie było w 2009 roku gotowych na tę zmianę (Martijn Verburg wThe Register).

Licencja Free Use

Użycie wydania 17 Javy od Oracle (oracle jdk) możliwe jest pod nową licencją, nazwaną Free Use Licence, która pozwala na rozwijanie i dystrybucję kodu w Javie - pod warunkiem niepobierania opłat - dla wszystkich użytkowników (nie tylko do użytku osobistego i deweloperskiego)

Wsparcie

Wsparcie dla Javy 17 typu podstawowego “Premier Support” trwa do września 2028, wsparcie rozszerzone do września 2031 (Java 17 roadmap)

Java 18

Na horyzoncie jest już JDK 18 z dwoma JEP-ami:

Tutaj warto mieć rękę na pulsie i sprawdzać, co nowego szykują deweloperzy Javy w niedalekiej przyszłości.