Java co nowego i co ciekawego w wersjach od 8 do 16

You are currently viewing Java co nowego i co ciekawego w wersjach od 8 do 16

Lista nowości z Javy od wersji 8 do 16

W tym artykule przedstawię Ci co się zmieniło w poszczególnych wersjach Java (od wersji 8 do wersji 16). Tak żebyś wiedział czego możesz się spodziewać pracując na konkretnej wersji.

Java 8

W Javie 8 wprowadzono wiele nowych rozwiązań, które znacznie uprościły pisanie w tym języku. Najważniejsze zmiany:

  • Wyrażenie Lambda
  • Interfejsy funkcyjne
  • Referencje do metod i konstruktorów
  • Wbudowane interfejsy funkcyjne
  • Strumienie (Stream API)
  • Domyślne metody w interfejsach
  • Wartości opcjonalne
  • API daty i czasu
Wyrażenie Lambda

Wyrażenie lambda są to funkcję anonimowe, które są zapisane w dużo prostszy sposób. Największym ich plusem jest poprawienie czytelności w kodzie.

(Type1 a, Type2 b...) -> { // logika }

// Przykład na podstawie tworzenia nowego wątku
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        // logika
    }
});

// Od Java 8
Thread t = new Thread(() -> {
    // logika
});
Interfejsy funkcyjne

Warunkiem koniecznym, żeby móc skorzystać z wyrażenia lambda jest interfejs z metodą abstrakcyjną. Wtedy taki interfejs możemy nazywać interfejsem funkcyjnym.

Aby nie narazić się na błędy podczas implementacji interfejsów funkcyjnych możemy dodać nad interfejsem adnotację @FunctionalInterface, która będzie mówić kompilatorowi, że jest interfejsem funkcyjnym i jeśli napotka jakiś problem to zgłosi błąd podczas kompilacji np. dodamy drugą metodą abstrakcyjną.

public class Main {

    @FunctionalInterface
    public interface IPrinter<T> {
        void print(T text);
    }

    public static void main(String[] args) {
        IPrinter<String> printer = (String text) -> System.out.println(text);
        printer.print("Some Text");
    }
}
Referencje do metod i konstruktorów

W sytuacji, w której nasze wyrażenie lambda jest pojedynczą metodą i do tej metody wkładamy parametr wejściowy z lambdy, możemy ułatwić jego zapis z użyciem słowa kluczowego ::.

// Na podstawie powyższego kodu
// Poniższą linijkę
IPrinter<String> printer = (String text) -> System.out.println(text);
// Przyjmujemy zmienną text na wejście i ustawiamy ją jako parametr metody w wyrażeniu lambda
// Dlatego szybszym zapisem będzie skorzystanie z ::, który uprości nam ten zapis
IPrinter<String> printer = System.out::println;

Podobnie sprawa ma się z konstruktorami. Jako przykład stworzyłem klasę Animal z wykorzystaniem interfejsu funkcyjnego .

public class Animal {
    String name;

    public Animal() {
    }

    public Animal(String name) {
        this.name = name;
    }
}

@FunctionalInterface
public interface AnimalFactory<T extends Animal> {
    T create(String name);
}

public class DemoApplication {
    public static void main(String[] args) {
        AnimalFactory<Animal> animalFactory = Animal::new;
        Animal animal = animalFactory.create("Piotr");
    }
}
Wbudowane interfejsy funkcyjne

Oprócz tego, że jest możliwość tworzenia własnych interfejsów funkcyjnych, są również dostępne wbudowane. Znajdują się w pakiecie java.util.function.

  • Function<T, R>
    • Metoda: apply
    • Działanie: przyjmuję obiekt typu T, zwraca obiekt typu R.
  • Consumer<T>
    • Metoda: accept
    • Działanie: przyjmuję obiekt typu T i nic nie zwraca (void).
  • Predicate<T>
    • Metoda: test
    • Działanie: przyjmuję obiekt typu T, zwraca flagę true/false.
  • Supplier<T>
    • Metoda: get
    • Działanie: nie przyjmuje żadnych parametrów, zwraca obiekt typu T.
  • UnaryOperator<T>
    • Metoda: apply
    • Działanie: przyjmuję obiekt typu T, zwraca obiekt typu T.
Function<Integer, String> function = (a) -> "Result is " + a;
String functionResult = function.apply(10);
System.out.println(functionResult);

Consumer<String> consumer = System.out::println;
consumer.accept("Some String and no return");

Predicate<String> predicate = (a) -> a != null && !a.isEmpty();
boolean predicateResult = predicate.test("String is not null and empty");
System.out.println(predicateResult);

Supplier<String> supplier = () -> "It is my random string";
String supplierResult = supplier.get();
System.out.println(supplierResult);

UnaryOperator<String> unaryOperator = (a) -> a + " TEST";
String unaryOperatorResult = unaryOperator.apply("SOME");
System.out.println(unaryOperatorResult);
Strumienie

Strumienie pozwalają na wykonywanie operacji na elementach kolekcji (np. List, Set, Map) bez konieczności za każdym razem przechowywania tych danych. Najważniejszymi zaletami strumieni jest możliwość wykonywania więcej niż jednej operacji na raz oraz działanie na wyrażeniach lambdach przez co kod staję się bardzo czytelny.

Uwaga! Nie mylić tych strumieni ze strumieniami wejścia/wyjścia.

Najważniejsze metody w strumieniach:

  • filter – pozwala na przefiltrowanie elementów kolekcji.
  • map – pozwala na przekonwertowanie elementu wejściowego na zupełnie inny typ.
  • sorted – sortowanie elementów.
  • distinct – usuń takie same elementy.
  • limit – zwróć maksymalne n elementów.
  • skip – pomiń n pierwszych elementów
  • reduce – operacja kończąca strumień, której wynikiem jest klasa Optional i zawiera pojedynczy obiekt.
  • collect – operacją, które pozwala przekonwertować strumień np. na kolekcję List, Set czy Map.
  • findFirst – znajdź pierwszy pasujący element. Zwraca obiekt typu Optional.
List<String> list = new ArrayList<>() {{
    add("N Value 2");
    add("N Value 3");
    add("Value 4");
    add("Value 4");
    add("Value 5");
    add("Value 1");
}};
List<String> newList = list.stream()
        // STAN LISTY: [N Value 2, N Value 3, Value 4, Value 4, Value 5, Value 1]
        // Weź wszystkie elementy, które zaczynają się "Value
        .filter((s) -> s.startsWith("Value"))
        // STAN LISTY: [Value 1, Value 4, Value 4, Value 5]
        // Do każdego elementu dodaj na końcu _000_111
        .map((s) -> s + "_000")
        // STAN LISTY: [Value 4_000, Value 4_000, Value 5_000, Value 1_000]
        // Sortujemy wartości i usuwamy zduplikowane elementy
        .sorted()
        .distinct()
        // STAN LISTY: [Value 1_000, Value 4_000, Value 5_000]
        .collect(Collectors.toList());
// Przed
System.out.println(list); // [N Value 2, N Value 3, Value 4, Value 4, Value 5, Value 1]
// Po zastosowaniu operacji na strumieniach
System.out.println(newList); // [Value 1_000, Value 4_000, Value 5_000]
Domyślne metody w interfejsach

Od tej wersji jest możliwe dodawanie domyślnego zachowania metod w interfejsach. Nie musimy teraz za każdym razem nadpisywać wszystkich metod w interfejsie jeśli chcemy, aby jakaś metoda wykonywała się przez nas w domyślny sposób. Wystarczy, że przed interesującą metodą dodamy słówko kluczowe default i możemy zacząć pisać jej domyślne zachowanie.

public interface ITest {
    default int add(int a, int b) {
        return a + b;
    }
}
Wartości opcjonalne

W Javie 8 wprowadzono klasę Optional, która opakowuję inną klasę i która zarządza nią czy jest pusta czy nie. Takie rozwiązanie poprawia nam czytelność kodu. Teraz nie musimy sprawdzać czy dany obiekt jest nullem, tylko skorzystamy z metod udostępnianych przez klasę Optional.

String someString = "UProgramisty.pl";

Optional<String> optional = Optional.of(someString);
if (optional.isPresent()) {
    System.out.println(optional.get());
}
API daty i czasu

Od Java 8 dostajemy nowe API daty i czasu.

Czas Lokalny (LocalTime). Nowe API pozwalające na wyświetlenie czasu i operowanie na nim.

LocalTime localTime = LocalTime.now();
System.out.println(localTime); // 20:38:01.793903400

Data Lokalna (LocalDate). Nowe API pozwalające na wyświetlenie daty i operowanie na nim.

LocalDate localDate = LocalDate.now();
System.out.println(localDate); // 2021-06-27

Połączenie daty i czasu (LocalDateTime). Nowa API, które jest połączeniem dwóch powyższych.

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2021-06-27T20:38:01.795897900
// Przykładowe operacje na czasach
LocalDateTime d1 = LocalDateTime.now();
LocalDateTime d2 = ChronoUnit.DAYS.addTo(d1, 3L);
LocalDateTime d3 = ChronoUnit.HOURS.addTo(d1, -3L);
long hoursBetween = ChronoUnit.HOURS.between(d3, d2);
System.out.println(d1); // 2021-06-27T20:44:12.974676200
System.out.println(d2); // 2021-06-30T20:44:12.974676200
System.out.println(d3); // 2021-06-27T17:44:12.974676200
System.out.println(hoursBetween + "h"); // 75h

Zegar (Clock). Umożliwia dostęp do aktualnej daty i czasu. Poniżej parę użyć tej klasy.

Clock clock = Clock.systemDefaultZone();

// Pobranie czasu jaki upłynął od początku epoki Uniks
long clockMills = clock.millis();
long systemMills = System.currentTimeMillis(); // wcześniejsze podejście

// Pobranie obecnego czasu instant
Instant instant1 = clock.instant();
Instant instant2 = Instant.now(); // klasyczne podejście

Strefa czasowa (ZoneId). Dzięki tej klasie w łatwy sposób możemy określić wartość przesunięcia strefy czasowej.

// Wyświetla wszystkie możliwe strefy czasowe
System.out.println(ZoneId.getAvailableZoneIds());
// [Asia/Aden, America/Cuiaba, ..., US/Pacific, Europe/Monaco]

// A jak chcemy sprawdzić jakie jest przesunięcie w danej strefie czasowej
ZoneId zoneId = ZoneId.of("Europe/Monaco");
System.out.println(zoneId .getRules()); // ZoneRules[currentStandardOffset=+01:00]

Java 9

W Javie 9 już nie wprowadzono tak wiele zmian jak w poprzedniej wersji, natomiast jest parę rzeczy, na które warto zwrócić uwagę:

  • Moduły
  • Metody fabrykujące dla kolekcji
  • Metody prywatne w interfejsach
  • Polepszenie klasy Optional
  • Ulepszenie Stream API
  • JShell
Moduły

Java Platform Module System – Od Java 9 wprowadzono możliwość tworzenia modułów, czyli zbioru pakietów i pliku opisującego zawartość danego modułu. Możemy wyróżnić 4 typy:

  • Systemowe
  • Aplikacyjne
  • Automatyczne
  • Anonimowe
Metody fabrykujące dla kolekcji

Od tej wersji Javy można tworzyć w prosty sposób kolekcje z użyciem metody fabrykującej of. Tworzone w ten sposób kolekcję są niemutowalne (nie możemy do niej dodać ani usunąć elementu)!

List.of("V1", "v2", "V3");
Set.of("V1", "v2", "V3");
Map.of(1, "V1", 2, "V2", 3, "V3");
// Lub alternatywne podejście dla tworzenia map
Map.ofEntries(Map.entry(1, "V1"), Map.entry(2, "V2"), Map.entry(3, "V3"));
Metody prywatne w interfejsach

Zeszła wersja Javy wprowadziła możliwość tworzenia domyślnych metod w interfejsach. W Javie 9 postanowiono bardziej zadbać o czystość kodu. W tym celu wprowadzono prywatne metody w interfejsach tak, żeby nie musieć już wszystkiego pisać w jednej metodzie.

public interface ITest {
    default int add(int a, int b) {
        print(a);
        print(b);
        return a + b;
    }
    
    private void print(Object text) {
        System.out.println(text);
    }
}
Polepszenie klasy Optional

Nowe metody dla klasy Optional:

  • or(Supplier supplier) – Metoda, która pozwola zawsze zwrócić jakiś Optional.
Optional<String> r = Stream.ofNullable("Some Value")
        .findFirst()
        // lub zwracamy inny optional jeśli ten pierwszy będzie pusty
        .or(() -> Optional.of("Some String")); 

// Wcześniej mieliśmy jeszcze dostępne 2 podobne metody, ale one służą do pobrania już konkretnej wartości
// orElse(T other)
r.orElse("Other String");
// orElseGet(Supplier supplier)
r.orElseGet(() -> "Other String");
  • stream() – zamieniamy klasę Optional na klasę Stream.
  • ifPresent(Consumer action) – pozwala na wykonanie jakieś operacji bezpośrednio po sprawdzeniu czy wartość nie jest pusta.
  • ifPresentOrElse() – j.w. oraz pozwala na wykonanie jakieś akcji w przypadku, gdy wartość jest pusta.
Ulepszenie Stream API

Nowe metody dla strumieni (Stream API):

  • takeWhile – Zwraca element strumienia dopóki warunek jest spełniony. Jako parametr wejściowy przyjmuje predykat (jednoargumentowa funkcja logiczna)
List<Integer> someList = List.of(1, 2, 3, 4, 5, 6);
someList.stream()
        .takeWhile(i -> i < 3)
        .forEach(System.out::println);
/**
 * Wynik:
 * 1
 * 2
 */
  • dropWhile – Zwraca element strumienia od kiedy warunek jest spełniony. Jako parametr wejściowy przyjmuje predykat.
List<Integer> someList = List.of(1, 2, 3, 4, 5, 6);
someList.stream()
        .dropWhile(i -> i < 5)
        .forEach(System.out::println);
/**
 * Wynik:
 * 5
 * 6
 */
  • iterate – metoda ta została przeciążone i teraz możemy do niej dać 3 parametr, który pozwoli ją w jakiś sposób ograniczyć. Wcześniej do tego celu wykorzystywaliśmy metodę limit.
IntStream.iterate(0, i -> i < 10, i -> i + 3)
        .forEach(System.out::println);
/**
 * Wynik:
 * 0
 * 3
 * 6
 * 9
 */
  • ofNullable – pozwala na stworzenie strumienia, który może przyjąć jako parametr wejściowy również wartość null.
List<Integer> someList = List.of(1, 2, 3);
List<String> r4 = Stream.ofNullable(someList)
        .flatMap(Collection::stream)
        .map(s -> s + "__NEW")
        .collect(Collectors.toList());
System.out.println(r4); // [1__NEW, 2__NEW, 3__NEW]

List<String> r5 = Stream.ofNullable(null)
        .map(s -> s + "__NEW")
        .collect(Collectors.toList());
System.out.println(r5); // []
JShell

Od Java 9 twórcy wprowadzili rozwiązanie, które pozwala bezpośrednio z konsoli wykonywać kod Javy. Jest to narzędzie podobne, które od zawsze istniało w innych język programowania m.in. Python czy PHP. Twórcy postanowili również takie coś wprowadzić właśnie dla Javy. Może się przydać w sytuacji, gdy chcemy na szybko przetestować niewielki fragment kodu bez konieczności specjalnie tworzenia dla niego statycznej metody main.

// W celu uruchomienia jshella należy uruchomić terminal i wpisać w dowolnym miejscu jshell
C:\Users\Uprogramisty>jshell
|  Welcome to JShell -- Version 15.0.2
|  For an introduction type: /help intro

jshell> String someString="Hello Wordl!";
someString ==> "Hello Wordl!"

jshell> System.out.println(someString);
Hello Wordl!

jshell>

Java 10

Nowości wprowadzone w Java 10 (od tej wersji wersję Javy wydawane są co pół roku w marcu i wrześniu):

  • Var dla zmiennych lokalnych
  • Nowe metody fabryczne dla kolekcji
  • Nowe rozwiązania podnoszące wydajność
Var dla zmiennych lokalnych

Od tej wersji Javy pojawił się nowy typ zmiennych var (JEP 286: Local-Variable Type Inference). Chodzi w nim o to, że jeśli kompilator jest w stanie wywnioskować typ zmiennej lokalnej to nie musi być ona deklarowana jawnie tylko wystarczy, że użyjemy słówka kluczowego var:

// Wcześniej
String someString = "Some String";
SomeObject someObject = new SomeObject();

// Od Java 10
var someString = "Some String";
var someObject = new SomeObject();
Nowe metody fabryczne dla kolekcji

Dodane zostały nowe metody dla kolekcji do łatwiejszego kopiowania:

  • List.copyOf()
  • Set.copyOf()
  • Map.copyOf()

Przykład pokazujący wykorzystanie metody List.copyOf(). Pokazuje, że wartość w skopiowanej liście się nie zmieniła:

List<String> someList = new ArrayList<>();
someList.add("Value 1");
someList.add("Value 2");

// Zostaje stworzona niemodyfikowalna kopia kolekcji
List<String> copiedSomeList = List.copyOf(someList);

// Modyfikujemy oryginalną listę
someList.add("Value 3");

System.out.println(someList); // [Value 1, Value 2, Value 3]
System.out.println(copiedSomeList); // [Value 1, Value 2]

Dodano również dla klasy Collectors (klasa wykorzystywana najczęściej w strumieniach) metody pozwalające na tworzenie niemodyfikowalnych kolekcji:

  • toUnmodifiableList
  • toUnmodifiableSet
  • toUnmodifiableMap

Przykład pokazujący wykorzystanie metody toUnmodifiableList:

List<String> someList = new ArrayList<>();
someList.add("V 1");
someList.add("V 2");

List<String> unmodifiableSomeList = someList.stream()
        .map(s -> "New " + s)
        .collect(Collectors.toUnmodifiableList());

System.out.println(unmodifiableSomeList); // [New V 1, New V 2]
Nowe rozwiązania podnoszące wydajność

W Javie 10 weszło również parę rzeczy związanych z przyśpieszeniem pracy w Garbage Collection (wprowadzone równoległe działanie procesów w ramach pełnego odśmiecania pamięci) czy przyśpieszenie startu JVM.

Java 11

Dla Javy 11 wprowadzono LTS (Long Term Support), czyli jest to takie główne wydanie wersji Javy, która będzie miała 3 letni okres utrzymywania (następną wersją będzie Java 17). A najważniejsze zmiany dla tej wersji Javy to:

  • Usprawnienie API
  • Var dla wyrażeń lambda
  • Możliwość uruchomienia programu java jedną komendą w terminalu
  • Usunięcie JavaFX z JDK
  • Poprawienie wydajności Garbage Collector
Usprawnienie API

Dodano nowe metody dla klasy String:

  • isBlank() – sprawdzenie czy string zawiera białe znaki
  • lines() – dzielenie stringa na linie i zwrócenie strumienia.
  • repeat(int x) – powiel string x razy.
  • strip() – usuwa białe znaki z początku i końca stringa.
  • stripLeading() – usuwa białe znaki z początku stringa.
  • stripTrailing() – usuwa białe znaki z końca stringa.

Poniżej przykład jak działa metoda lines() i repeat(int count) w praktyce:

String lines =  "line 1 \nline 2 \n".repeat(2);
lines.lines().forEach(s -> System.out.println(s));

/**
 * Wynik
 * line 1
 * line 2
 * line 1
 * line 2
 */

W klasie Optional dodano metodę isEmpty(), która sprawdza czy wartość obiektu jest null (jest to tak naprawdę odwrotność metody isPresent(), która sprawdza czy wartość nie jest null).

Dodano też metody dla klasy Files:

  • readString(Path path) – odczytaj zawartość pliku w postaci Stringa. Dużym plusem tej metody jest, że sama zadba o otwarcie i zamknięcie pliku.
  • writeString(Path path, CharSequence csqm OpenOption… options) – zapisz tekst do pliku.
  • isSameFile(Path path, Path path2) – sprawdza czy pliki są takie same.
Var dla wyrażeń lambda

W Javie 10 wprowadzone słowo kluczowe var. Wcześniej było dostępne tylko dla zmiennych lokalnych, a teraz dodano możliwość stosowania ich w wyrażeniach lambda.

// wcześniej
Function<Integer, Integer> pow1 = (Integer x) -> x * x;

// od Java 11
Function<Integer, Integer> pow2 = (var x) ->  x * x;

// Zawsze można zapisać bez typów
Function<Integer, Integer> pow2 = (x) ->  x * x;
Możliwość uruchomienia programu java jedną komendą w terminalu

Do tej pory jak chcieliśmy uruchomić aplikację napisaną w Javie z poziomu terminalu to musieliśmy najpierw skompilować naszą aplikację jedną komendą, a następnie uruchomić drugą. W Javie 11 postanowiono ułatwić to i teraz jest możliwość uruchomienia programu Java jedną komendą w terminalu.

// Wcześniej
javac Main.java
java Main

// Od Java 11
java Main.java
Usunięcie JavaFX z JDK

Od Javy 11 biblioteka do tworzenia interfejsów graficznych nie będzie dostępna w JDK. Jeśli będziesz chciał z niej skorzystać to musisz ją dodatkowo dodać do projektu.

Poprawienie wydajności Garbage Collector

Wprowadzono 2 nowy Garbage Collectory (oba są eksperymentalne):

  • Epsilon – Sam w sobie nie czyści pamięci. Wykorzystuje się go w testach na zadaniach, które potrzebują niskiego poziomu opóźnienia działania.
  • ZGC – Bardzo wydajny GC. Na razie obsługiwany tylko przez system Linux 64-bitowy.

Java 12

Nowości wprowadzone w Java 12:

  • Wyrażenie Switch (Preview)
  • Ulepszenie Grabage Collector G1 – optymalizacja zużycia pamięci
  • Ulepszenie Grabage Collector G1 – anulowanie mieszanych kolekcji
  • JVM Constant API – JVM API do zarządzania pulą stałych
  • Microbenchmark Suite – narzędzie do testowania kodu dla developerów JDK np. testy wydajnościowe
  • One AArch64 Port, Not Two – usuwa jeden port arm64 i zostawia jeden.
  • Default CDS Archives – zmiana, która przyśpiesza uruchomianie aplikacji.
Wyrażenie Switch (Preview)

Najważniejsza zmiana z punktu widzenia programisty. W Java 12 przygotowaną wstępną wersję funkcjonalności polecenia Switch bazującej na wyrażeniach lambda (funkcjonalność jest na razie dostępne w trybie 'Preview’ i jeśli chcemy ją włączyć musimy ustawić specjalną flagę –enable-preview).

int v = 2;
// Stara Wersja
switch (v) {
    case 1:
    case 2:
        System.out.println("Value 1 or 2");
        break;
    case 3:
        System.out.println("Value 3");
        break;
    case 4:
        System.out.println("Value 4");
        break;
}

// Od Java 12
switch (v) {
    case 1, 2 -> System.out.println("Value 1 or 2");
    case 3 -> System.out.println("Value 3");
    case 4 -> System.out.println("Value 4");
}

// Albo nawet możemy przypisać do zmiennej wynik switcha
String result = switch (v) {
    case 1, 2 -> "Value 1 or 2";
    case 3 -> "Value 3";
    case 4 -> {
        int a = 1;
        break "Value 4";
    }
    default -> "Value Default";
};
JVM Constant API – JVM API do zarządzania pulą stałych

Jest to ułatwienie, które ma na celu ładowanie stałych na poziomie bytecodu tak, żeby nie musieć ładować całej klasy w celu zaczytania tylko stałych. Dzięki temu unikamy konieczności obsługi szeregu błędów jakie mogą wystąpić właśnie podczas ładowania całej klasy.

Java 13

Nowości wprowadzone w Java 13:

  • Poprawione wyrażenie Switch (Second Preview)
  • Bloki tekstów (Preview)
  • Ulepszenie Grabage Collector – ZGC
  • Dynamiczne archiwa CDS
  • Implementacja Socket API
Poprawione wyrażenie Switch (Second Preview)

Od Java 12 jest możliwe zwrócenie wartości w Switchu. Jeśli był blok tekstu to na końcu pisaliśmy break i obok wartość zwracaną. Od Java 13 zamieniono słówko break na yield. W tej wersji funkcjonalność jest nadal w trybie 'preview’ i tak jak poprzednio należy ustawić flagę –enable-preview, aby ją uruchomić.

String result = switch (v) {
    case 1, 2 -> "Value 1 or 2";
    case 3 -> "Value 3";
    case 4 -> {
        int a = 1;
        yield "Value 4"; // Od Java 13 jest tutaj słówko yield. Wcześniej napisalibyśmy break.
    }
    default -> "Value Default";
};
Bloki tekstów (Preview)

Wreszcie od Java 13 możemy pisać bloki tekstów, zamiast dla każdej nowej linijki robić konkatenacje. Tak jak poprzednio jest to funkcjonalność w trybie 'preview’, więc jeśli chce się jej używać to trzeba najpierw ją włączyć.

// Wcześniej
String someString1 = "Line 1\nLine 2\nLine 3\n";
//lub
String someString2 = "Line 1\n" +
        "Line 2\n" +
        "Line 3\n";
// Od Java 13
String someString3 = """
        Line 1
        Line 2
        Line 3
        """;
Ulepszenie Grabage Collector – ZGC

Poprawiono Garbage Collector ZGC. Tak jak to było możliwe w przypadku G1 czy Shenandoah firmy Red Hat, tak również dla tego typu Garbage Collectora umożliwiono zwracanie nieużywanej pamięci sterty do systemu operacyjnego.

Dynamiczne archiwa CDS

Rozszerzenie mechanizmu CDA (class-data sharing). Nowy mechanizm pozwala na dynamiczną archiwizacją klas podczas wyłączania aplikacji, które nie znalazły się w podstawowej wersji archiwum CDS. Ta nowość została wprowadzono po to, aby móc jeszcze bardziej przyśpieszyć uruchamiania się aplikacji napisanych w Javie.

Implementacja Socket API

Wcześniejsza wersja Socket API pochodzi z JDK 1.0 oraz była taką mieszanką Javy i C. Tak jak się domyślasz praca nad nim nie należała do najprzyjemniejszych, a debugowanie go sprawiało dużo problemów. Twórcy postanowili ponownie ją zaimplementować, tak żeby była bardziej nowoczesna i była łatwiejsza w utrzymaniu oraz debugowaniu.

Java 14

Nowości wprowadzone w Java 14:

  • Wyrażenie Switch w standardzie
  • Rekordy (Preview)
  • Pattern matching w instanceof (Preview)
  • Poprawa wyjątku NullPointerException
  • Poprawki bloków tekstowych (Second Preview)
  • Poprawki Garbage Collector
  • Dostęp do pamięci zewnętrznej API (Inkubator)
  • Narzędzie do pakowania aplikacji Javy (Inkubator)
Wyrażenie Switch w standardzie

Nowe wyrażenie Switch weszło do standardu i teraz już można używać bez ustawiania specjalnych flag.

W nowej wersji switch należy pamiętać, że składa się z wyrażeń lambda i można zwrócić wynik do zmiennej z użyciem słówka kluczowego yield.

String result = switch (v) {
    case 1, 2 -> "Value 1 or 2";
    case 3 -> "Value 3";
    case 4 -> {
        int a = 1;
        yield "Value 4";
    }
    default -> "Value Default";
};
Rekordy (Preview)

Od Java 14 zostały wprowadzone w wersji 'preview’ rekordy, które mają za zadanie usprawnić pisanie klas poprzez zmniejszenie ilości kodu generowanego przez programistę m.in. gettery i settery, konstruktory, toString, equals i hashcode.

Przed Java 14, aby uniknąć tego problemu stosowano bibliotekę Lombok, a teraz Java ma swoją własną wersje.

// Wcześniej
public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

// Od Java 14
public record Point(int x, int y) {}

Warto zaznaczyć, że wszystkie klasy typu rekord są niezmienne i nie można po nich dziedziczyć oraz wszystkie pola w klasie są prywatne i finalne.

Pattern matching w instanceof (Preview)

Usprawnienie funkcjonalności instanceof (preview, należy włączyć flagę –enable-preview, aby działało). Teraz jak w warunku sprawdzimy czy faktycznie obiekt jest danego typu to automatycznie zostanie skonwertowany na ten typ. Wcześniej musieliśmy go rzutować na ten typ, mimo że wiadomo było jakiego jest.

Object someObject = "TEST";

// Wcześniej
if (someObject instanceof String) {
    String someString = (String) someObject;
    System.out.println(someString);
}

// Od Java 14
if (someObject instanceof String) {
    System.out.println(someObject);
}
Poprawa wyjątku NullPointerException

Poprawiono wyjątek NullPointerException, tak żeby zawierał więcej informacji przydatnych dla programisty podczas szukania przyczyny wystąpienia. Bardzo dobrze się sprawdza jak mamy do czynienia z zagnieżdżoną strukturą.

package pl.uprogramisty;

public class Main {
    public static void main(String[] args) {
        Person person = new Person(new PersonData());
        person.getData().getPersonName().getName();
    }
}

/**
 * Wynik:
 * Exception in thread "main" java.lang.NullPointerException: Cannot invoke "pl.uprogramisty.PersonName.getName()" because the return value of "pl.uprogramisty.PersonData.getPersonName()" is null
 * 	at pl.uprogramisty.Main.main(Main.java:6)
 */

public class Person {
    private PersonData data;

    public Person(PersonData data) {
        this.data = data;
    }

    public PersonData getData() {
        return data;
    }
}

public class PersonData {
    private PersonName personName;

    public PersonName getPersonName() {
        return personName;
    }
}

public class PersonName {
    private String name;

    public String getName() {
        return name;
    }
}
Poprawki bloków tekstowych (Second Preview)

Jeśli chodzi o bloki tekstów do dużo się nie zmieniło od poprzedniej wersji. Doszły 2 specjalne sekwencje:

  • \ – linia tekstu nie pojawia się w końcowym łańcuchu znaków.
  • \s – nie usuwa białych znaków w końcowym łańcuchu znaków.
Poprawki Garbage Collector

Dodano parę poprawek dla Garbage Collectora:

  • Usunięto Concurrent Mark Sweep GC
  • Jako deprecated (przestarzała) oznaczono algorytmy ParallelScavenge GC i SerialOld GC. Jako zamiennik można zastosować algorytm Parallel.
  • Poprawienie wydajności dla G1 GC. Dodano w nim wsparcie dla NUMA (non-uniform memory access). Pozwala na ustawienie dla dostępu do pamięci rdzeni różnej wydajności.
  • Dla ZGC GC dodano porty dla systemów Windows i MacOS. Został wprowadzony w Java 11 i tam były dostępne porty tylko dla systemów Linux.
Dostęp do pamięci zewnętrznej API (Inkubator)

Jako główne założenie tej inicjatywy jest stworzenie API, które będzie miało bezpieczny dostęp do pamięci (sterta pamięci Javy) zewnętrznej.

Narzędzie do pakowania aplikacji Javy (Inkubator)

W Javie nigdy nie było dobrego narzędzie, które pozwalałoby na budowanie pakietów instalacyjnych w zależności od systemu operacyjnego. Tak więc od Javy 14 wprowadzono takie narzędzie. W zależności od systemu operacyjnego, będzie tworzył plik wyjściowy o odpowiednim typie:

  • Windows -> exe i msi
  • Linux -> deb i rpm
  • MacOS -> pkg i dmg
// przykład uruchomienia
jpackage --input target/ \
   --name someApp \
   --main-jar someApp.jar \ 
   --type deb \
   --main-class pl.uprogramisty.someApp \
   --java-options '--enable-preview'

Java 15

Nowości wprowadzone w Java 15:

  • Bloki tekstowe
  • Pattern Matching w instanceof (Second Preview) – nic szczególnie się nie zmieniło od poprzedniej wersji
  • Rekordy (Second Preview) – nic szczególnie się nie zmieniło od poprzedniej wersji
  • Sealed Classes (Preview)
  • Hidden Classes
  • Grabage Collector ZGC i Shenandoah
  • Przepisanie DatagramSocket API – przepisanie API, żeby było bardziej nowoczesne. Wcześniejsza wersja była jeszcze z JDK 1.0.
  • Dostęp do pamięci zewnętrznej API (Second Inkubator)
Bloki tekstowe

Od teraz bloki tekstowe są już na stałe wprowadzone do Javy (Zostały wstępnie wprowadzone w Java 13).

Sealed Classes (Preview)

Sealed Classes (klasy zapięczentowane) to takie, którym jesteśmy wstanie ograniczyć możliwość dziedziczenia po niej tylko do konkretnych klas. W tym celu twórcy wprowadzili dwa słowa kluczowe: sealed i permits.

public abstract sealed class Vehicle permits Car, Truck { ... }
Hidden Classes

Kolejną nowością są klasy ukryte (Hidden Classes). Klasy, które są głównie dedykowane programistą tworzącym frameworki i biblioteki. Główne założenie ich jest takie, że nie będzie można się do nich odwołać w standardowy sposób. Będzie to możliwe tylko z wykorzystaniem mechanizmu refleksji.

Grabage Collector ZGC i Shenandoah

Od Java 15 Garbage Collector ZGC i Shenandoah wchodzą w fazę produkcyjną (wcześniej były dostępne tylko jako taka faza eksperymentalna).

Dostęp do pamięci zewnętrznej API (Second Inkubator)

W tej wersji ulepszono API, które pozwoli na lepszy dostęp do pamięci zewnętrznej (wcześniej używano sun.misc.Unsafe)

Java 16

Nowości wprowadzone w Java 16:

  • Rekordy
  • Pattern Matching w instanceof
  • Sealed Classes (Second Preview) – nic szczególnie się nie zmieniło od poprzedniej wersji
  • Vector API (Inkubator)
  • Elastic Metaspace – ułatwienie, które pozwoli na mniejsze obciążenie pamięci oraz zostanie uproszczony kod.
  • Foreign Linker API (Incubator)
  • Dostęp do pamięci zewnętrznej API (Third Inkubator) – nic szczególnie się nie zmieniło od poprzedniej wersji
  • Dodano do Javy narzędzie jpackage (wcześniej było w inkubatorze) – więcej info w zmianach w Java 14.
Rekordy

Od Java 16 można używać już rekordów bez żadnych dodatkowych flag. Są już w wersji produkcyjnej. Bardziej szczegółowy opis o nich przeczytasz w zmianach w Java 14.

Pattern Matching w instanceof

Podobnie jak wyżej, Pattern Matching wszedł do wersji produkcyjnej i można go już swobodnie używać. Tutaj również bardziej szczegółowy opis znajdziesz w zmianach w Java 14.

Vector API (Inkubator)

Vector API jak sama nazwa wskazuje będzie pozwalał na operację i obliczenia na wektorach (JEP 338).

Foreign Linker API

Foreign Linker API jest to API do łatwiejszego dostania się do natywnego kodu. W przyszłości ma zastąpić mechanizm JNI.

Podsumowanie

Po przeczytaniu artykułu już wiesz co nowego jest w poszczególnych wersjach Javy. Jak widzisz większość nowych wersji Javy nie wprowadza jakiś szczególnych zmian (No może oprócz wersji 8, gdzie wprowadzano lamdy i strumienie). Raczej skupiane są na pojedynczych nowościach i ulepszaniu starych funkcjonalności.

Subscribe
Powiadom o
guest
0 komentarzy
Inline Feedbacks
View all comments