Вышла общедоступная версия 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 года).