Java 23 w praktyce

Jakie zmiany w języku i bibliotece standardowej są warte uwagi? Krótki przewodnik dla zabieganych programistów.
Java 23 w praktyce
Eksperymenty z palca
Dzięki wprowadzeniu w ostatnich wersjach importu modułów, niejawnie deklarowanych klas i metod main oraz uruchamianiu programu posiadającego wiele plików źródłowych możemy teraz wesoło eksperymentować z javą bez konieczności tworzenia projektu w gradle/maven oraz bez odpalania ciężkiego IDE.
- nie trzeba definiować żadnej klasy, wystarczy metoda
main(klasa zostanie utworzona niejawnie) - main nie musi mieć parametrów (jeśli nie potrzebujemy ich używać)
- main nie musi być
public static - można korzystać z innych plików (pod warunkiem, że są w nich zdefiniowane klasy publiczne)
- w niejawnych klasach (oraz w sesjach JShell uruchomionych z parametrem
--enable-preview) wszystkie publiczne klasy zjava.basesą automatycznie zaimportowane - dzięki nowej klasie
java.io.IO(która jest automatycznie zaimportowana) można używać metodyprintbez tego javovego “ogona”System.out.println
Bardzo mnie to cieszy - odpalam sobie neovima i zaczynam zabawę (btw, odkryłam ostatnio neovimcraft.com, polecam) . Wczoraj na przykład testowałam wypisywanie kolorowych napisów przy użyciu instrukcji sterujących do terminala. Oto pełna zawartość pliku Colors.java:
// https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
// Cube colors:
//16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
static void printWithColor(String s, int color) {
print("\033[38;5;%sm%s\033[m".formatted(color, s));
}
void main(String[] args) {
var text = args.length < 1 ? "code" : args[0];
for (int r = 0; r < 6; r++) {
for (int g = 0; g < 6; g++) {
for (int b = 0; b < 6; b++) {
var v = 16 + 36*r + 6*g + b;
printWithColor(text, v);
}
}
print("\n");
}
}Do uruchomienia wystarczy polecenie
java --enable-preview Colors.javaUżywam małego skryptu jav, który dodałam do swojej $PATH i dzięki któremu mogę korzystać z uzupełniania nazw plików. Oto jav:
java --enable-preview "$@"
String Templates
W javie 23 zniknęły String Templates. Będę za nimi tęsknić. Mam nadzieję, że zostanie opracowana sensowna alternatywa dla dotychczas proponowanych ukośników.
Wątki wirtualne
O wątkach pisałam już jakiś czas temu (Virtual Threads w Javie 21) Pierwsze doniesienia z frontu wskazują na potrzebę mądrego yżywania synchornizacji: wątek wirtualy wchodzący do sekcji kodu synchronized zostaje przypięty (ang. pinned) do wątku “nosiciela” (carrier thread) i blokuje go (tj. wątek wirtualny nie może zostać “odmontowany”) - patrz: problem z biblioteką jsoup o diagnozowaniu pewnego problemu ze współbieznością) - co w przypadku serwerów obsługujących wiele połączeń może szybko doprowadzić do wykorzystania wszystkich dostępnych wątków platformowych bądź do znacznego ograniczenia wydajności systemu.
Strategie polecane do tego, aby zmniejszyć ryzyko pinningu:
- użycie
ReentrantLocksz pakietujava.util.concurrent.locks: pozwala na odmontowanie wątku wirtualnego gdy jest on zatrzymany na locku - przegląd kodu: zwracamy uwagę na to, aby synchronized nie był zbyt często używany
- diagnozowanie pinningu przy użyciu flagi
-Djdk.tracePinnedThreads=full
Scoped Values
O tym też napisałam co nieco w artykule Jeśli nie ThreadLocal to co? ScopedValue!. Java 23 to już trzecie “preview”: pojawiła się statyczna metoda ScopedValue.callWhere przyjmująca interfejs funkcyjny. Strukturalna współbieżność (structuredTaskScope) pozwala na elekganckie utworzenie drzewa wątków mających dostęp do niemutowalnych, ale nadpisywalnych w głębszych wywołaniach ScopedValues.
private static final ScopedValue<String> X = ScopedValue.newInstance();
void foo() {
ScopedValue.runWhere(X, "hello", () -> bar());
}
void bar() {
System.out.println(X.get()); // prints hello
ScopedValue.runWhere(X, "goodbye", () -> baz());
System.out.println(X.get()); // prints hello
}
void baz() {
System.out.println(X.get()); // prints goodbye
}Structured Concurrency
ScopedValues doskonale sprawdzą się w sytuacji, gdy uruchamiane na wątkach wirtualnych zadania powinny mieć dostęp do zniennych “thread local” w bezpieczny sposób. Uruchamianie zadań przy pomocy strukturalnej współbieżności jeszcze nie doczekało się wielu przykładów użycia, więc kradnę przykład ze strony https://openjdk.org/jeps/480:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // Join both subtasks
.throwIfFailed(); // ... and propagate errors
// Here, both subtasks have succeeded, so compose their results
return new Response(user.get(), order.get());
}
}JavaDoc i Markdown
W Java 23 można używać komentarzy w markdown. Oto przykład:
/// This is the traditional "Hello World!" program.
/// We can use also some links:
/// * a module [java.base/]
/// * a package [java.util]
/// * a class [String]
/// * a field [String#CASE_INSENSITIVE_ORDER]
/// * a method [String#chars()]
public class HelloWorld {
public static void main(String... args) {
System.out.println("Hello World!");
}
}Odnośniki mogą mieć tekst alternatywny; można używać tabel. I należy pamiętać o sekwencji unikowej podczas wypisywania nawaisów kwadratowych:
/// | Latin | Greek |
/// |-------|-------|
/// | a | alpha |
/// | b | beta |
/// | c | gamma |
public void main() {
/// some code
/// with a link where we have to escape square brackets [String#copyValueOf(char\[\])]
System.out.println("foo");
}Patterns, rekordy i typy podstawowe
Java jest powoli wzbogacana możliwością użycia pattern matchingu. Jego niewątpliwą, choć nieczęsto podkreślaną zaletą jest “wzorzec poznawczy” - to cecha nie tyle ważna dla kompilatora czy projektu, co dla programisty. Wybór działania na podstawie warunku może być elegancko przedstawiony w wyrażeniu switch, gałęzie są dobrze uwidocznione w strukturze kodu, a czytelnik takiego fragmentu nie musi “skakać” między gałęziami if-elsów bądź operatora ?:.
W javie 23 mamy możliwość (preview) użycia typów podstawowych we wzorcach:
switch (x.getYearlyFlights()) {
case 0 -> ...;
case 1 -> ...;
case 2 -> issueDiscount();
case int i when i >= 100 -> issueGoldCard();
case int i -> ... appropriate action when i > 2 && i < 100 ...
}Niestety, nie są jeszcze wspierane stałe we wzorcach rekordów:
record Box(short s) {}
Box b = ...
switch (b) {
case Box(42) -> ... // Box(42) is not a valid record pattern
case Box(int i) -> ...
}Tutaj Rust błyszczy jasno na niebie:
#[derive(Debug)]
struct Box(i32);
fn main() {
let v = Box(12);
match v {
Box(60) => println!("kopa!"),
Box(12) => println!("mendel!"),
Box(x) if x > 100 => println!("ponad sto"),
_ => println!("hmmm"),
}
}SequencedCollection
W Javie 21 pojaiwł się nowy interfejs SequencedCollection i podinterfejsy SequencedSet i Sequenced Map.
Elementy w kolekcji mają dobrze zdefiniowany porządek (encounter order). Nowe metody w tym interfejsie:
addFirst()addLast()getFirst()getLast()reversed().
Jest on implementowany przez całkiem sporo kolekcji:
AbstractList, AbstractSequentialList, ArrayDeque, ArrayList, AttributeList, ConcurrentLinkedDeque, ConcurrentSkipListSet, CopyOnWriteArrayList, LinkedBlockingDeque, LinkedHashSet, LinkedList, RoleList, RoleUnresolvedList, Stack, TreeSet, Vector,
Źródła
Garść odnośników
Podstawowym źródłem wiedzy o tym, w jaki sposób zmienił się język Java, jest strona openjdk z odnośnikami do JEP-ów w kolejnych wersjach (patrz “Wersja po wersji” poniżej). Kolejnym są notatki do wydania javy 23 na stronie Oracle, wśród których znaleźć można m.in.
- Java API specification (na razie nie wygląda to na wersję do końca oficjalną)
- różnice w API względem Javy 22
- Aneks do specyfikacji języka
Żródłem, które odkryłam stosunkowo niedawno, a które z pewnoscią jest znacznie bardziej lekkostrawne niż suchy język specyfikacji, jest https://foojay.io/, a w nim:
Wersja po wersji
Poniżej lista odnośników do wszystkich JEP-ów:
- Primitive Types in Patterns, instanceof, and switch (Preview)
- Class-File API (Second Preview)
- Markdown Documentation Comments
- Vector API (Eighth Incubator)
- Stream Gatherers (Second Preview)
- Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
- ZGC: Generational Mode by Default
- Module Import Declarations (Preview)
- Implicitly Declared Classes and Instance Main Methods (Third Preview)
- Structured Concurrency (Third Preview)
- Scoped Values (Third Preview)
- Flexible Constructor Bodies (Second Preview)
- Region Pinning for G1
- Statements before super(…) (Preview)
- Foreign Function & Memory API
- Unnamed Variables & Patterns
- Class-File API (Preview)
- Launch Multi-File Source-Code Programs
- String Templates (Second Preview)
- Vector API (Seventh Incubator)
- Stream Gatherers (Preview)
- Structured Concurrency (Second Preview)
- Implicitly Declared Classes and Instance Main Methods (Second Preview)
- Scoped Values (Second Preview)
- String Templates (Preview)
- Sequenced Collections
- Generational ZGC
- Record Patterns
- Pattern Matching for switch
- Foreign Function & Memory API (Third Preview)
- Unnamed Patterns and Variables (Preview)
- Virtual Threads
- Unnamed Classes and Instance Main Methods (Preview)
- Scoped Values (Preview)
- Vector API (Sixth Incubator)
- Deprecate the Windows 32-bit x86 Port for Removal
- Prepare to Disallow the Dynamic Loading of Agents
- Key Encapsulation Mechanism API
- Structured Concurrency (Preview)
- UTF-8 by Default
- Simple Web Server
- Code Snippets in Java API Documentation
- Reimplement Core Reflection with Method Handles
- Vector API (Third Incubator)
- Internet-Address Resolution SPI
- Foreign Function & Memory API (Second Incubator)
- Pattern Matching for switch (Second Preview)
- Deprecate Finalization for Removal
Zakończenie
Na zakończenie mały bonus: zachęcam do obejrzenia The best of Java Shorts Show - przegląd małych programików, klas, trików, jakie są dostępne programistom bez konieczności instalowania frameworków. To jak powiew świeżego powietrza i przypomnienie, jak dobrze można się bawić mając do dyspozycji jedynie bibliotekę standardową Javy.
This text is part of the series java...
- 2025-21-03 - Java 24 Overview
- 2024-01-11 - Java 23 w praktyce
- 2021-27-11 - JDK 18 proposals: Java 18
- 2021-21-09 - Java 17 - RandomGenerator i spółka
- 2021-15-09 - Java 17 - co nowego?
- 2021-04-03 - Java 15 - czym są sealed classes?
- 2021-26-02 - Java 13 i 14: Bloki tekstowe i rekordy
- 2021-24-02 - Java 12 - wyrażenie switch (preview feature)
- 2021-23-02 - Java 11 - HTTP Client i uruchamianie jednoplikowych programów
- 2021-18-02 - Java 9 - co to jest JShell i dlaczego warto używać REPL-a w Javie
- 2021-12-02 - Java 10 - var, nowe metody w Optional, kolekcje "unmodifiable"
- 2021-11-02 - Java 9 - nowości w bibliotece
- 2021-10-02 - Java 9: czy mogę stworzyć z mojej aplikacji binarkę?
- 2021-10-02 - Java 9: praktyczny przykład - trzy moduły
- 2021-08-02 - Java 8: praktyczny przykład - przewidywanie kolejnej daty w serii