Вышла Java 19

bgo8dlvzdctyexjwltcs_-q5ygc.png

Вышла общедоступная версия Java 19. В этот релиз попало более двух тысяч закрытых задач и 7 JEP’ов. Release Notes можно посмотреть здесь. Изменения API — здесь.

Ссылки на скачивание:

Вот список JEP’ов, которые попали в Java 19.

Паттерн-матчинг для switch (Third Preview) (JEP 427)

Паттерн-матчинг для switch, который появился в Java 17 в режиме preview и остался на второе preview в Java 18, всё ещё остаётся в этом статусе. Это первый случай в Java, когда языковой конструкции не хватило двух релизов, чтобы стать стабильной: ранее все конструкции укладывались в два preview.

В этом релизе в паттерн-матчинг было внесено два главных изменения.

Во-первых, охранные паттерны && были заменены на условия when:

// --enable-preview --release 18:
switch (obj) {
    case Integer x && x > 0 -> ...;
    default -> ...;
}
// --enable-preview --release 19:
switch (obj) {
    case Integer x when x > 0 -> ...;
    default -> ...;
}

О мотивации такого изменения можно прочитать в рассылке проекта Amber.

Во-вторых, было изменено поведение матчинга null. Теперь null матчится только в ветке case null и большие ни в каких других, включая тотальных:

// --enable-preview --release 18:
Object obj = null;
switch (obj) {
    case Object x -> ...; // matches because total pattern
}
// --enable-preview --release 19:
Object obj = null;
switch (obj) {
    case Object x -> ...; // NPE
}
// --enable-preview --release 19:
Object obj = null;
switch (obj) {
    case null -> ...; // OK
    case Object x -> ...; 
}

Про причины такого изменения можно также прочитать в рассылке.

Паттерны записей (Preview) (JEP 405)

Паттерн-матчинг дополнился новым видом паттерна: паттерн записей.

Раньше для паттерн-матчинга записей был доступен только паттерн по типу с дальнейшим ручным извлечением компонентов:

record Point(int x, int y) {}

static void printSum(Object o) {
    if (o instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x + y);
    }
}

С паттернами записей код становится существенно компактнее:

static void printSum(Object o) {
    if (o instanceof Point(int x, int y)) {
        System.out.println(x + y);
    }
}

Паттерны записей могут быть вложенными:

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}

static void printCoordinatesAndColor(ColoredPoint cp) {
    if (cp instanceof ColoredPoint(Point(var x, var y), var c)) {
        System.out.println("x = " + x);
        System.out.println("y = " + y);
        System.out.println("color = " + c);
    }
}

Также паттерны записей могут быть именованными:

static void printObject(Object obj) {
    if (obj instanceof Point(var x, var y) p) {
        System.out.println("point = " + p);
        System.out.println("x = " + x);
        System.out.println("y = " + y);
    }
}

Кроме того, паттерны записей хорошо сочетаются со switch из предыдущего JEP’а:

static void printObject(Object obj) {
    switch (obj) {
        case Point(var x, var y) when x > 0 && y > 0 ->
            System.out.println("Positive point: x = " + x + ", y = " + y);
        case Point(var x, var y) ->
            System.out.println("Point: x = " + x + ", y = " + y);
        default -> System.out.println("Other");
    }
}

Virtual Threads (Preview) (JEP 425)

В Java появились виртуальные потоки в режиме preview.

Виртуальные потоки, в отличие от потоков операционной системы, являются легковесными и могут создаваться в огромном количестве (миллионы экземпляров). Это свойство должно значительно облегчить написание конкурентных программ, поскольку позволит применять простой подход «один запрос — один поток» и не прибегать к более сложному асинхронному программированию. При этом миграция на виртуальные потоки уже существующего кода должна быть максимально простой, потому что виртуальные потоки являются экземплярами существующего класса java.lang.Thread, а значит, большую часть существующего кода не придётся переписывать.

Виртуальные потоки реализованы поверх обычных потоков и существуют только для JVM, но не для операционной системы (отсюда и название «виртуальные»). Поток, на котором в данный момент работает виртуальный поток, называется потоком-носителем. Если потоки платформы полагаются на планировщик операционной системы, то планировщиком для виртуальных потоков является ForkJoinPool. Когда виртуальный поток блокируется на некоторой блокирующей операции, то он размонтируется от своего потока-носителя, что позволяет потоку-носителю примонтировать другой виртуальный поток и продолжить работу. Такой режим работы и малый размер виртуальных потоков позволяет им очень хорошо масштабироваться. Однако на данный момент есть два исключения: synchronized блоки и JNI. При их выполнении виртуальный поток не может быть размонтирован, поскольку он привязан к своему потоку-носителю. Такое ограничение может препятствовать масштабированию. Поэтому при желании максимально использовать потенциал виртуальных потоков рекомендуется избегать synchronized блоки и операции JNI, которые выполняются часто или занимают длительное время.

Для создания виртуальных потоков и работы с ними появилось следующее API:


  • Thread.Builder — билдер потоков. Например, виртуальный поток можно создать путём вызова Thread.ofVirtual().name("name").unstarted(runnable).
  • Thread.startVirtualThread(Runnable) — создаёт и сразу же запускает виртуальный поток.
  • Thread.isVirtual() — проверяет, является ли поток виртуальным.
  • Executors.newVirtualThreadPerTaskExecutor() — возвращает исполнитель, который создаёт новый виртуальный поток на каждую задачу.

Для виртуальных потоков также добавилась поддержка в дебаггере, JVM TI и Java Flight Recorder.

Виртуальные потоки разрабатываются с 2017 года в рамках проекта Loom.

Structured Concurrency (Incubator) (JEP 428)

Ещё одним результатом работы над проектом Loom стало добавление в Java нового API для Structured Concurrency.

Structured Concurrency — это подход многопоточного программирования, который заимствует принципы из однопоточного структурного программирования. Главная идея такого подхода заключается в следующем: если задача расщепляется на несколько конкурентных подзадач, то эти подзадачи воссоединяются в блоке кода главной задачи. Все подзадачи логически сгруппированы и организованы в иерархию. Каждая подзадача ограничена по времени жизни областью видимости блока кода главной задачи.

В центре нового API класс StructuredTaskScope. Пример использования StructuredTaskScope, где показана задача, которая параллельно запускает две подзадачи и дожидается результата их выполнения:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future user = scope.fork(() -> findUser());
    Future order = scope.fork(() -> fetchOrder());

    scope.join();           // Join both forks
    scope.throwIfFailed();  // ... and propagate errors

    return new Response(user.resultNow(), order.resultNow());
}

Может показаться, что в точности аналогичный код можно было бы написать с использованием ExecutorService и submit(), но у StructuredTaskScope есть несколько принципиальных отличий, которые делают код безопаснее:


  • Время жизни всех потоков подзадач ограничено областью видимости блока try-with-resources. Метод close() гарантированно не завершится, пока не завершатся все подзадачи.
  • Если одна из операций findUser() и fetchOrder() завершается ошибкой, то другая операция отменяется автоматически, если ещё не завершена (в случае политики ShutdownOnFailure, возможны другие).
  • Если главный поток прерывается в процессе ожидания join(), то обе операции findUser() и fetchOrder() отменяются.
  • В дампе потоков будет видна иерархия: потоки, выполняющие findUser() и fetchOrder(), будут отображаться как дочерние для главного потока.

Новое API должно облегчить написание многопоточных программ благодаря знакомому структурному подходу. Пока API имеет инкубационный статус, оно будет находиться в модуле jdk.incubator.concurrent и одноимённом пакете.

Foreign Function & Memory API (Preview) (JEP 424)

Foreign Function & Memory API, которое было в инкубационном статусе в Java 17 и Java 18, теперь стало Preview API. Оно находится в пакете java.lang.foreign.

Vector API (Fourth Incubator) (JEP 426)

Векторное API, которое уже было в инкубационном статусе три релиза (Java 16, Java 17, Java 18), продолжает в нём находиться. Пока API не выйдет из инкубационного статуса, оно будет находиться в модуле jdk.incubator.vector.

Linux/RISC-V Port (JEP 422)

JDK теперь официально портирован под архитектуру Linux/RISC-V.

Заключение

Java 19 не является LTS-релизом и будет получать обновления от Oracle только в течение полугода (до марта 2023 года). Однако Azul обещает выпускать обновления Zulu как минимум до марта 2025 года (2.5 года).

© Habrahabr.ru