Java 18

W skrócie

Java 18 ustanawia kodowanie UTF-8 jako domyślne, wprowadza prosty serwer webowy oraz poprawia dodawanie fragmentów kodu do dokumentacji. Nadal trwają prace nad dopasowywaniem wzorców w switchu, a metoda finalize() została oznaczona jako "do usunięcia".

Java 18 w kontekście innych wersji

Java 18 została wydana 22 marca 2022 roku. Jest to wydanie z krótkim, półrocznym okresem wsparcia, aż do wydania Java 19. Warto wspomnieć, że poprzednia wersja LTS (Long Time Support) to Java 17, a kolejna to Java 21.

Przegląd zmian

Zmiany wprowadzone w tej wersji są raczej zakulisowe i nie możemy się tutaj spodziewać nowych składni języka.

JEP 400: UTF-8 by Default

Do tej pory kodowanie znaków zależne było od systemu operacyjnego, na którym uruchamiany był program. Plik o nazwie "hello.txt" o zawartości "こんにちは" zapisany na macOS był odczytywany w następujący sposób:

java.io.FileReader("hello.txt") -> "こんにちは" (macOS)
java.io.FileReader("hello.txt") -> "ã?"ã‚"ã?<<ã?¡ã? " (Windows (en-US))
java.io.FileReader("hello.txt") -> "縺ォ縺。縺ッ" (Windows (ja-JP)

Dało się temu zapobiec, ustawiając dodatkowy parametr -Dfile.encoding=UTF8, więc większość projektów właśnie tak sobie z tym radziła. Od Java 18 nie będzie potrzeby ustawiania tej wartości, bo UTF-8 będzie domyślnym kodowaniem. Jeśli jakiś projekt polegał na poprzednim podejściu do kodowania znaków, to może ustawić -Dfile.encoding=COMPAT w celu zachowania dotychczasowej funkcjonalności.

Link do JEP 400

JEP 408: Simple Web Server

Wprowadzono prosty serwer webowy do hostowania plików. Mocnym założeniem jest to, żeby nie było w nim za dużo funkcjonalności, bo to jest już realizowane przez inne znakomite rozwiązania: Jetty, Netty, Grizzly, Apache Tomcat, Apache httpd, Nginx. Tutaj chodzi o to, żeby dało się go wystartować jednym poleceniem.

Po co komu taki serwer? Głównie chodzi o sytuacje związane z edukacją, prototypowaniem czy testowaniem. Wystarczy wpisać polecenie jwebserver do cmd i serwer zostanie uruchomiony.

Link do JEP 408

JEP 413: Code Snippets in Java API Documentation

Do tej pory przedstawianie fragmentów kodu w dokumentacji było realizowane za pomocą @code i wyglądało następująco:

<pre>{@code
    Optional<Path> p =
        uris.stream().filter(uri -> !isProcessedYet(uri))
                      .findFirst()
                      .map(Paths::get);
}</pre>

Powyższy fragment to część dokumentacji metody Optional.map, wygenerowaną na tej podstawie dokumentację znajdziesz tutaj.

Takie rozwiązanie ma kilka problemów:

  • Narzędzia nie mają dobrego sposobu na znajdowanie i przez to walidację składni w takich fragmentach
  • Nie da się utworzyć niezawodnego mechanizmu kolorowania składni dla takich fragmentów
  • Brak wsparcia IDE do tworzenia takich fragmentów, przez co jest brak podpowiadania składni
  • Nie można w nich używać składni HTML
  • Takie fragmenty nie mogą zawierać komentarzy dokumentujących
  • Są problemy ze wcięciami, są one zależne od początku komentarza

Rozwiązaniem ma być wprowadzenie @snippet, który ma wyglądać następująco:

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet :
 * if (v.isPresent()) {
 *     System.out.println("v: " + v.get());
 * }
 * }
 */

Co ciekawe, takie snippety będą mogły również linkować do zewnętrznych plików.

/**
 * The following code shows how to use {@code Optional.isPresent}:
 * {@snippet file="ShowOptional.java" region="example"}
 */

Gdzie zawartość ShowOptional.java to:

public class ShowOptional {
    void show(Optional<String> v) {
        // @start region="example"
        if (v.isPresent()) {
            System.out.println("v: " + v.get());
        }
        // @end
    }
}

Link do JEP 413

JEP 416: Reimplement Core Reflection with Method Handles

Nie ma to wpływu na API mechanizmu refleksji. Celem zadania jest ujednolicenie wewnętrznej implementacji, bo do tej pory istniały 3 sposoby na realizację tych samych funkcjonalności. Projekt Valhalla zakłada zmiany dotykające bytecode, który powoduje, że każda zmiana w tym module zostać wykonana trzykrotnie, co wprowadzało niepotrzebne koszty.

Link do JEP 416

JEP 417: Vector API (Third Incubator)

Dalsza część prac nad API do wyrażania obliczeń na wektorach. Celem jest wprowadzenie spójnego interfejsu do pracy na wektorach, które pozwoli maksymalnie wykorzystać wsparcie sprzętowe. Poniżej film omawiający to zagadnienie.

Link do JEP 417

JEP 418: Internet-Address Resolution SPI

Aktualnie Java korzysta z systemowych sposobów na pobieranie informacji o nazwie hosta (ang. hostname). Jest to problematyczne, bo zapytanie o nazwę hosta jest blokujący i ciężko to połączyć z wirtualnymi wątkami, które ma zamiar wprowadzić Projekt Loom. Dodatkowe motywacje to:

  • Wsparcie dla nowych sposobów pozyskiwania adresów, np. DNS over QUIC, TLS, czy HTTPS
  • Możliwość lepszego dostosowywania pobierania nazwy hosta do aktualnego przypadku (pierwsze co mi przychodzi do głowy to whitelista/blacklista niektórych domen)
  • Testowanie --- będzie możliwość mockowania pozyskiwania adresów

Z tego co udało mi się znaleźć to został wprowadzony nowy interfejs InetAddressResolver, posiada on aktualnie 2 implementacje:

  • java.net.InetAddress.PlatformResolver
  • java.net.InetAddress.HostsFileResolver

Obie istnieją już od Java 9, ale teraz można zarejestrować własną implementację korzystając z InetAddressResolverProvider.

Link do JEP 418

JEP 419: Foreign Function & Memory API (Second Incubator)

To zadanie ma na celu zastąpienie JNI, leciwego już sposobu uruchamiania natywnych metod. Ma to być zupełnie nowe API, a nie zmiany w JNI. Ogólnie będzie wygodniej, szybciej i lepiej.

Link do JEP 419

JEP 420: Pattern Matching for switch (Second Preview)

Część projektu Amber. Jest to drugi, ale jeszcze nieostateczny (dla JDK 19 zaplanowany jest trzeci) podgląd dopasowywania do szablonu w switchu. Weźmy na przykład taki kawałek kodu:

    record Point(int i, int j) {}
    enum Color { RED, GREEN, BLUE; }

    static void typeTester(Object o) {
        switch (o) {
            case null     -> System.out.println("null");
            case String s -> System.out.println("String");
            case Color c  -> System.out.println("Color with " + c.values().length + " values");
            case Point p  -> System.out.println("Record class: " + p.toString());
            case int[] ia -> System.out.println("Array of ints of length" + ia.length);
            default       -> System.out.println("Something else");
        }
    }

Tutaj obiekt o może być dopasowany typu klasy, typu enuma, typu tablicy, a oprócz tego do null czy default. Nadal trwają prace nad tym mechanizmem. Rozważane są przypadki brzegowe, sytuacje gdzie obiekt pasuje do kilku wzorców, albo gdzie nie wszystkie możliwości są objęte switchem. Zainteresowanym polecam poczytać szczegóły. Link poniżej.

Link do JEP 420

JEP 421: Deprecate Finalization for Removal

Przygotowanie gruntu pod usunięcie metody Object.finalize() z języka. Co ciekawe ta metoda została oznaczona jako deprecated już a Java 9, natomiast teraz zyskała dodatkową flagę forRemoval=true. Przypomnę tylko, że ta metod jest automatycznie wywoływana przez Garbage Collector na chwilę przed usunięciem obiektu z pamięci. Została wprowadzona na samym początku języka i jest to niejako pozostałość po C / C++ gdzie zwalnia się zasoby manualnie. W Javie nie pamięcią zarządza Wirtualna Maszyna, więc nie ma potrzeby zwalniania zasobów samodzielnie.

Wraz z tą zmianą wprowadzono:

  • Dodatkowy parametr (--finalization=disabled), który pozwala na wyłączenie tego mechanizmu (metoda finalize() nie będzie wywoływana przez JVM). Uwaga! Uruchomienie tej flagi może powodować nieprzewidywalne zachowanie (bo finalize() jest używana również w ramach JDK), więc należy używać tylko na środowiskach testowych
  • Nowy zestaw statystyk dla JDK Flight Recorder (JFR), zbierające dane o finalizacji podczas działania programu

Następujące metody zostały oznaczone @Deprecated(forRemoval=true:

  • java.lang.Object.finalize()
  • java.lang.Enum.finalize()
  • java.awt.Graphics.finalize()
  • java.awt.PrintJob.finalize()
  • java.util.concurrent.ThreadPoolExecutor.finalize()
  • javax.imageio.spi.ServiceRegistry.finalize()
  • javax.imageio.stream.FileCacheImageInputStream.finalize()
  • javax.imageio.stream.FileImageInputStream.finalize()
  • javax.imageio.stream.FileImageOutputStream.finalize()
  • javax.imageio.stream.ImageInputStreamImpl.finalize()
  • javax.imageio.stream.MemoryCacheImageInputStream.finalize()

Link do JEP 421

Podsumowanie

Pomimo braku nowych funkcjonalności, to widać, że Java nadal się stabilnie rozwija. Konsekwentnie rozwijane są nowe funkcjonalności i pokazywane społeczności do testów. Moim zdaniem dobrze, że nie ma pochopnych i impulsywnych decyzji, które powodowałby wprowadzenie nieprzemyślanych funkcji do języka.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.