Spis treści

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 z java.baseautomatycznie zaimportowane
  • dzięki nowej klasie java.io.IO (która jest automatycznie zaimportowana) można używać metody print bez 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 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

1
java --enable-preview Colors.java

Uż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:

1
java --enable-preview "$@"

/posts/java-23-w-praktyce/colors.png

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 ReentrantLocks z pakietu java.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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

/// 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/// | 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:

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
7
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#[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.

Ż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:

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.