Od dzisiaj nowa Java wchodzi w fazę Rampdown – oznacza to, że lista funkcji została zamrożona i nie należy spodziewać się dalszych nowości. Dlatego przejdziemy sobie przez pełną listę zmian w nowej edycji.
Stable
JEP 423: Region Pinning for G1
JEP 423 ma na celu zredukowanie opóźnień w G1 poprzez implementację tak zwanego “przypinania regionów”, dzięki czemu nie ma potrzeby wyłączania mechanizmów Garbage Collection w krytycznych regionach Java Native Interface.
Krytyczny region JNI odnosi się do specyficznej sekcji kodu, w której wątek Javy uzyskuje bezpośredni dostęp do obiektów Javy. W tym regionie, wątek może bezpośrednio manipulować danymi obiektu, co jest kluczowe dla współpracy z “niezarządzanymi” językami programowania, takimi jak C i C++. Oryginalny JEP dość przystępnie opisuje ten koncept.
Pierwotny problem: Kiedy wątek znajduje się w krytycznym regionie JNI, JVM musiał unikać przesuwania znajdujących się w nim obiektów podczas procesu odśmiecania. G1 dezaktywuje GC w każdym krytycznym regionie, co znacząco wpływa na jego opóźnienia w przypadku kodu JNI.
Rozwiązanie: JEP rozszerza G1 o możliwość przypinania dowolnych regionów. Polega to na utrzymaniu licznika krytycznych obiektów w każdym regionie: zwiększania go przy uzyskaniu krytycznego obiektu i zmniejszania przy jego zwolnieniu. Regiony z niezerową liczbą są uważane za “przypięte” i nie są ewakuowane podczas GC.
JEP 454: Foreign Function & Memory API
JEP 454 koncentruje się na zastąpieniu JNI prostszym, czytelnym API w “czystej” Javie, zapewniając jednocześnie lepszą wydajność, szerokie wsparcie platform oraz spójność i integralność operacji na natywnym kodzie i danych.
Pierwotny problem: Programiści Javy nie raz potrzebują pracować z zasobami znajdującymi się poza bezpośrednią kontrolą JVM, szczególnie kodu napisanego w językach kompilowanych typu C i danych znajdującej się w natywnej pamięci. Jednocześnie, obecne API nie udostępniają na to łatwego i bezpiecznego sposobu To ograniczenie utrudnia płynną interakcję z innymi platformami oraz wykorzystanie natywnych bibliotek i danych, co jest mocną stroną takiego choćby Pythona czy C.
Rozwiązanie: JEP 454 wprowadza API “Funkcji Zewnętrznych i Pamięci (FFM API)”, aby sprostać tym wyzwaniom. API pozwala programom Java na efektywne wywoływanie funkcji zewnętrznych (kodu spoza JVM) i bezpieczny dostęp do zewnętrznej pamięci (pamięci niezarządzanej przez JVM), zastępując kruche, niebezpieczne i ogólnie nielubiane JNI. API ma na celu umożliwienie programom Java wywoływania natywnych bibliotek i przetwarzania danych natywnych bardziej niezawodnie (wprowadzenie typowania) i elastycznie.
Prosty przykład kodu: Prostym przypadkiem użycia API FFM jest uzyskanie uchwytu metody dla funkcji biblioteki C, takiej jak radixsort
, i jej użycie do sortowania tablicy ciągów znaków zainicjowanej w Javie.
JEP 456: Unnamed Variables & Patterns
JEP 456 ma na celu poprawę czytelności i utrzymywalności kodu poprzez umożliwienie deweloperom deklarowania zmiennych, których tworzenie jest wymagane w określonych kontekstach, ale nie są realnie używane, a także poprawę obsługi wzorców w instrukcjach switch i case.
Pierwotny problem: Deweloperzy często deklarują zmienne, których nie zamierzają używać, czy to ze względów stylistycznych, czy z powodu wymagań składni języka. Niejawność założenia, że będą one nieużywane może prowadzić do błędów i niejasności w kodzie. Mogą także mylić wszelkiej maści lintery i inne narzędzia wspomagające prace z kodem, których pojawia się coraz więcej.
Rozwiązanie: JEP 456 wprowadza “nienazwane” zmienne i wzorce, które można stosować, gdy deklaracje zmiennych lub zagnieżdżone wzorce są wymagane, ale nigdy nie używane. Oba typy są oznaczane za pomocą znaku podkreślenia, _. Pozwala to na wyraźne wskazanie, że dana zmienna lub parametr lambdy jest nieużywany, poprawiając tym samym czytelność i utrzymywalność kodu, a także lepsze wsparcie narzędzi.
W praktyce:
Unnamed Variable<br>for (Order _ : orders) { // Nieoznaczona zmienna<br> total++;<br>}
Unnamed Pattern<br> switch (obj) {<br> case Integer _:<br> System.out.println("To jest Integer, ale jego wartość nie jest potrzebna.");<br> break;<br> // Inne przypadki...<br> }<br>
JEP 458: Launch Multi-File Source-Code Programs
JEP 458 ułatwia iteracyjne przejście od tworzenia prostych programów do bardziej złożonych, umożliwiając płynne tworzenie i uruchamianie aplikacji złożonych z wielu plików źródłowych bez potrzeby manualnego kompilowania każdej z nich.
PS: Całość przyjemnie komponuje się z 463: Implicitly Declared Classes and Instance Main Methods (Second Preview), który znajdziecie trochę niżej
Pierwotny problem: Historycznie, uruchamianie programów napisanych w Javie wymagało kompilacji kodu źródłowego do plików .class
przed wykonaniem. JEP 330 umożliwił uruchamianie pojedynczych plików źródłowych bezpośrednio, ale program musiał się w całość mieścić w jednym pliku .java
, a wiecie…
Rozwiązanie: JEP 458 rozszerza funkcjonalność uruchamiania programów przez polecenie java
, umożliwiając uruchamianie programów dostarczanych jako wiele plików źródłowych Java. Teraz możliwe jest uruchomienie programu złożonego z wielu plików .java
bez konieczności ich kompilacji, co ułatwia pisanie skryptów.
W praktyce: Jeśli mamy dwa pliki, Main.java i Helper.java, można uruchomić Main.java za pomocą java Main.java
, a program automatycznie znajdzie i skompiluje Helper.java (w niektórych przypadkach może być niezbędne zdefiniowanie struktury czy classpath).
Zainstaluj teraz i czytaj tylko dobre teksty!
Preview
JEP 447: Statements before super(…) (Preview)
JEP wprowadza nowe pojęcie „kontekstu przed-konstrukcyjnego”, który obejmuje zarówno argumenty wyraźnego wywołania konstruktora przez super
, jak i wszystkie instrukcje, które występują przed nim. W kontekście tym obowiązują zasady podobne do normalnych metod instancji, z wyjątkiem, że kod nie może odnosić się do tworzonej instancji.
Pierwotny problem: Dotychczas w konstruktorach wymagane było, aby pierwszą instrukcją był wywołanie konstruktora klasy nadrzędnej super(...)
lub innego konstruktora tej samej klasy this(...)
. To ograniczenie utrudniało wstawienie logiki potrzebnej do stworzenia argumentów dla super
, która musiała być umieszczana w statycznych metodach pomocniczych, pośrednich konstruktorach pomocniczych lub argumentach konstruktora.
Rozwiązanie: JEP 447 wprowadza zmianę w “gramatyce” konstruktorów, pozwalając na umieszczenie instrukcji, które nie odnoszą się do tworzonej instancji, przed wyraźnym wywołaniem konstruktora. Celem jest zapewnienie większej swobody w wyrażaniu zachowania konstruktorów, jednocześnie zachowując gwarancję, że konstruktory działają “od góry do dołu” podczas tworzenia instancji klasy.
W praktyce:
class SubClass extends SuperClass {
SubClass(int param) {
// Logika przed konstruktorem klasy nadrzędnej
int calculatedValue = someStaticMethod(param);
super(calculatedValue); // Wywołanie konstruktora klasy nadrzędnej
// Dodatkowa logika po wywołaniu super(...)
}
private static int someStaticMethod(int param) {
return param * 2;
}}
JEP 457: Class-File API (Preview)
Celem JEP 457 jest dostarczyć standardowe API do parsowania, generowania i transformowania plików klas Java
Pierwotny problem: W ekosystemie Javy, pliki klasy są kluczowe do parsowania, generowania i transformacji. Istniejące biblioteki do przetwarzania plików klasy, takie jak ASM, BCEL czy Javassist, nie zawsze nadążają za szybkimi zmianami formatu plików klasy wprowadzanymi w JDK. To prowadzi do problemów z niezgodnością wersji i błędów widocznych dla programistów aplikacji.
Rozwiązanie: JEP 457 proponuje standardowe API do parsowania, generowania i transformacji plików klasy Java, które będzie śledzić format pliku class
zdefiniowany przez Specyfikację JVM. To umożliwi migrację komponentów JDK właśnie na nie, ostatecznie eliminując potrzebę używania wewnętrznej kopii biblioteki ASM w JDK.
Dlaczego teraz? Twórcy przekonują, że ewolucja JVMa znacznie przyspieszyła i coraz więcej zmian odbywa się właśnie na poziomie generowanego kodu. Dzięki nowemu API duże inicjatywy jak np. Valhalla ma być mniej uciążliwe dla całego ekosystemu.
Przykład parsowania plików klasy z wzorcami:<br> CodeModel code = ...<br> Set<ClassDesc> deps = new HashSet<>();<br> for (CodeElement e : code) {<br> switch (e) {<br> case FieldInstruction f -> deps.add(f.owner());<br> case InvokeInstruction i -> deps.add(i.owner());<br> // ... i tak dalej dla instanceof, cast, itd.<br> }<br> }<br>
JEP 459: String Templates (Second Preview)
JEP 459 wprowadza “szablony tekstowe” jako nowy rodzaj wyrażenia w Javie. Szablony tekstowe ułatwiają tworzenie długich ciągów znaków, łącząc zhardcodowany tekst z osadzonymi wyrażeniami i procesorami szablonów, w celu uzyskania specjalizowanych wyników.
Pierwotny problem: Programiści Java rutynowo tworzą ciągi znaków, łącząc tekst stały z wyrażeniami. Istniejące metody składania ciągów w Javie, takie jak konkatenacja za pomocą operatora +, są niewygodne i mogą prowadzić do trudnego w utrzymaniu kodu.
Rozwiązanie:
String Templates to nowy rodzaj konstruktu w Java, które umożliwiają interpolację ciągów znaków, ale w sposób bardziej zaawansowany niż istniejące rozwiązania. String Templates zapewniają bezpiecznie i efektywnie komponować ciągi znaków.
Przykładowo, procesor szablonu STR wykonuje interpolację ciągów znaków, zastępując każde osadzone wyrażenie w szablonie jego (sformatowaną) wartością.
Przykład kodu:<br>
String name = "Joan";<br>
String info = STR."My name is \{name}";<br>
assert info.equals("My name is Joan"); // true<br>
JEP 461: Stream Gatherers (Preview)
Interfejs Stream Gatherer reprezentuje transformację elementów Streamu. Gatherers mogą transformować elementy w różnorodny sposób: jeden do jednego, jeden do wielu, wiele do jednego, wiele do wielu. Mogą też śledzić wcześniej widziane elementy, aby wpłynąć na transformację późniejszych elementów, a także przerwać przetwarzanie, aby przekształcić nieskończone strumienie na skończone, umożliwiając ich zrównoleglone wykonanie.
Jaki problem rozwiązują?: Chociaż Stream API od czasu JDK 1.8 zapewnia bogaty zestaw operacji pośrednich i końcowych, takich jak mapowanie, filtrowanie, redukcja czy sortowanie, nie posiadało możliwości rozszerzenia tego zestawu. To oznacza, że niektóre złożone zadania nie mogą być łatwo wyrażone jako streamy, ponieważ niezbędna operacja pośrednia nie istniała.
Co przynosi JEP: JEP 461 wprowadza operację pośrednią Stream::gather(Gatherer)
do Stream API, umożliwiającą przetwarzanie elementów strumienia za pomocą zdefiniowanego przez użytkownika zbioru operacji zwanych „gathererami”. Pozwala to na rozszerzenie możliwości Stream API, które teraz odwzorować mogą prawie każdą operację pośrednią.
Przykład kodu:
Załóżmy, że zadanie polega na wzięciu strumienia ciągów znaków i uczynieniu go unikalnym, ale z unikalnością opartą na długości ciągu, a nie na jego zawartości:
var result = Stream.of("foo", "bar", "baz", "quux")
.gather(new DistinctByLengthGatherer())
.toList();
public class DistinctByLengthGatherer implements Gatherer<String, String, Set<Integer>, List<String>> {
@Override
public Supplier<Set<Integer>> supplier() {
return HashSet::new;
}
@Override
public BiConsumer<Set<Integer>, String> accumulator() {
return (seen, string) -> {
if (seen.add(string.length())) {
seen.add(string);
}
};
}
@Override
public BinaryOperator<Set<Integer>> combiner() {
return (seen1, seen2) -> {
seen1.addAll(seen2);
return seen1;
};
}
@Override
public Function<Set<Integer>, List<String>> finisher() {
return ArrayList::new;
}
}
W tym przykładzie, hipotetyczna operacja .gather(new DistinctByLengthGatherer())
jest przykładem zastosowania Gatherera.
JEP 462: Structured Concurrency (Second Preview)
JEP 462 wprowadza API dla strukturalnej współbieżności, co ułatwia zarządzanie grupami powiązanych zadań działających w różnych wątkach jako pojedynczą jednostkę pracy. To podejście upraszcza obsługę błędów i anulowanie, poprawiając niezawodność i obserwowalność.
Pierwotny problem: Programowanie współbieżne może być skomplikowane, szczególnie przy zarządzaniu wieloma zadaniami wykonywanymi równolegle. Istniejące podejścia często wiążą się z ryzykiem wycieków wątków, opóźnień w anulowaniu, a także problemami z obserwowalnością kodu współbieżnego.
Rozwiązanie:
Kluczową klasą API strukturalnej współbieżności jest StructuredTaskScope
z pakietu java.util.concurrent
. Pozwala ona strukturyzować zadanie jako grupą współbieżnych podzadań i koordynować ich jako jednostki w ramach konkretnej struktury. Podzadania są wykonywane w własnych wątkach, a następnie zarządzanie, łączone i anulowane, w tym anulowane kaskadowo.
Przykład użycia:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier user = scope.fork(() -> findUser());
Supplier order = scope.fork(() -> fetchOrder());
scope.join()
.throwIfFailed();
return new Response(user.get(), order.get());
}
}
JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)
Wprowadza możliwość pisania metody main
bezpośrednio na poziomie pliku źródłowego, bez konieczności umieszczania ich w klasie, co upraszcza strukturę kodu, szczególnie dla początkujących.
Pierwotny problem: Java, będąc dobrym narzędziem do tworzenia dużych, złożonych aplikacji, może być wyzwaniem dla początkujących programistów, zwłaszcza w porównaniu z takim Pythonem. Pierwsze kroki w nauce Javy często wiążą się z koniecznością zrozumienia i stosowania skomplikowanych konceptów, które są niepotrzebne przy pisaniu prostych programów.
Rozwiązanie: JEP 463 wprowadza do Javy niejawnie deklarowane klasy i metody main dla instancji, co ułatwia pisanie prostych programów. Pozwala to na bardziej intuicyjne i mniej obciążające podejście do tworzenia początkowych programów, jednocześnie zachowując pełną kompatybilność i możliwość rozszerzenia do bardziej zaawansowanych funkcji języka, gdy umiejętności programisty rosną.
Przykład w praktyce:
void main() {
System.out.println("Hello, World!");
}
Ten przykład pokazuje, jak za pomocą uproszczonej struktury można napisać program wypisujący „Hello, World!” bez konieczności jawnego deklarowania klasy i statycznej metody main.
JEP 464: Scoped Values (Second Preview)
JEP 464 wprowadza „scoped values”, które umożliwiają bezpieczne i wydajne przekazywanie “uwięzionych” w konkretnym scope, niemutowalnych danych w obrębie pojedynczego wątku, a także między nimi. Stanowią one preferowaną alternatywę dla Thread.Local
, szczególnie przy użyciu dużej liczby wirtualnych wątków.
Pierwotny problem: W Java, metody często wymagają przekazywania danych jako parametrów, co może być niepraktyczne, gdy każdy pośredni wywoływany potrzebuje różnych danych. Często tworzony jest więc mutowany kontekst, który jest swoistym “workiem na dane”, przekazywanym za pomocą parametrów lub trzymany w pamięci wątku”
Rozwiązanie: „Scoped values” pozwalają na udostępnianie danych między metodami w dużym programie bez użycia parametrów metod. Są one typu ScopedValue
i zwykle deklarowane jako final static
z prywatnym dostępem, aby zapobiec bezpośredniemu dostępowi z innych klas.
Przykład użycia:<br>ScopedValue.where(NAME, <value>)<br> .run(() -> { ... NAME.get() ... call methods ... });
W tym przykładzie, ScopedValue.where
definiuje scoped value i obiekt, do którego ma być związany. Wywołanie run
wiąże scoped value, dostarczając kopię specyficzną dla bieżącego wątku, a następnie wykonuje przekazane wyrażenie lambda. Podczas trwania wywołania run
, wyrażenie lambda lub każda metoda wywołana bezpośrednio lub pośrednio z tego wyrażenia, może odczytać scoped value za pomocą metody get()
.
Zainstaluj teraz i czytaj tylko dobre teksty!
Inkubacja
JEP 460: Vector API (Seventh Incubator)
Krótki opis: JEP 460 rozwija API umożliwiające wyrażenie obliczeń wektorowych, kompilujących się do natywnych instrukcji wektorowych na obsługiwanych architekturach CPU, zapewniając wydajność przewyższającą równoważne obliczenia skalarne.
Pierwotny problem: Obliczenia wektorowe, które pozwalają na równoczesne wykonywanie operacji na wielu danych, były trudne do wyrażenia w Javie i zależały od algorytmu auto-wektoryzacji HotSpot, co ograniczało ich użyteczność i wydajność w praktyce.
Rozwiązanie: Vector API w Javie umożliwia tworzenie złożonych algorytmów wektorowych z większą przewidywalnością i niezawodnością, z wykorzystaniem istniejącego auto-wektoryzatora HotSpot, ale z bardziej przewidywalnym modelem dla użytkownika.
Co się zmieniło od poprzedniego inkubatora: W JDK 22, API zostało ponownie wprowadzone do inkubacji z drobnymi usprawnieniami w stosunku do JDK 21, w tym poprawkami błędów i usprawnieniami wydajności. Dalej czeka ono na finalizacje prac nad Project Valhalla.