Вышла общедоступная версия 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<String> user = scope.fork(() -> findUser());
    Future<Integer> 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 года).

Комментарии (12)


  1. phillennium
    20.09.2022 22:57
    +2

    Я недавно задавался вопросом «какой может быть открывающая картинка для поста про Java 19», там в реплаях нагенерили изображение нейросетями по запросу «Duke, the Java mascot discovers new Java features in a treasure chest». Получилось не совсем то, но раз тут пост про Java 19 без открывающей картинки, пусть тогда оно здесь будет :)




  1. Scratch
    21.09.2022 07:26
    -6

    Как там честные дженерики и unsigned int поживают, уже сделали?


    1. RigelGL
      21.09.2022 09:17
      +2

      Честные дженерики потребуют изменения структуры jvm, что приведёт к отказу от обратной совместимости. Если очень нужно разделять List<A> от List<B> и менять архитектуру нельзя/невозможно, то решением будет наследование от списка с реализацией метода Class getElementClass().

      Так ли часто нужен unsigned? На практике редко.
      Для unsigned int есть Integer.compareUnsigned(a, b), и другие функции. То же для остальных примитивов.


      1. leonidv
        21.09.2022 10:07
        +2

        Там где часто нужны unsigned, используются другие языки: C/C++/Rust. В JVM мире очень редко, соглашусь с вами.


    1. Maccimo
      21.09.2022 11:31

      Unsigned не нужен.


      1. kovserg
        21.09.2022 13:49
        +1

        Комплексные числа тоже не нужны?


        1. Maccimo
          22.09.2022 11:01
          +2

          Кому-то и кватернионы или арифметика с насыщением могут быть нужны. Но это не значит, что это обязательно должно быть частью языка общего назначения, а не специализированной библиотекой.


          1. kovserg
            22.09.2022 14:23

            А что уже можно операторы перегружать?


      1. OldNileCrocodile
        21.09.2022 19:30
        -1

        Natural without sign is sucks. Use tolerant integers)


      1. AnthonyMikh
        22.09.2022 19:17

        Ещё как нужны, громадное количество величин по смыслу не могут быть отрицательными


  1. Revertis
    21.09.2022 13:38
    +1

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

    А кто сказал, что компактнее == читабельнее? Я считаю, что читабельность упала.


  1. OldNileCrocodile
    21.09.2022 19:26

    Изменения pattern-matching для "switch" всё более похожи на копирастию с костылями. Взяли тот же "when" из C#, который к тому же относился к statement-switch, а не expression-switch. Нет бы заменить "&&", "||" на нормальные слова "and" и "or". Чувствую, что ещё не решили, поэтому пока Preview.