Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top

Вебинар: SAST как Quality Gate - 13.03

>
>
>
Нововведения Java 24

Нововведения Java 24

12 Мар 2025

Уже скоро, 18 марта, выйдет новая версия Java. Поэтому предлагаю посмотреть, какие в ней будут новшества, включая финализацию давно ожидаемых Stream Gatherers!

Порядок нововведений (JEP — JDK Enhancement Proposal) выбран по собственной оценке их "интересности", а не по их нумерации.

JEP 485: Stream Gatherers

Как известно, операции Stream API разделяются на промежуточные (порождающие новый Stream) и терминирующие (создающие результат или имеющие побочный эффект). Однако у терминирующих есть collect(Collector), позволяющий создавать свои операции посредством реализации Collector. А вот список промежуточных расширить нельзя: ограничиваемся map, flatMap, filter, distinct, sorted, peek, sorted. Во всяком случае, так было до Java 24, ведь в ней введены Stream Gatherers.

Ключевые моменты нововведения состоят в следующем:

1. В java.util.stream.Stream добавлен метод gather(Gatherer).

2. Добавлен интерфейс java.util.stream.Gatherer. Его спецификация заключается в четырёх методах:

  • initializer — создание изначального промежуточного состояния. Использует Supplier;
  • integrator — обработка элементов, опциональное использование промежуточного состояния, отправка результата далее в поток. Использует новый функциональный интерфейс Integrator;
  • combiner — объединение состояний. Использует BinaryOperator;
  • finisher — последняя операция использования промежуточного состояния и отправки результата далее в поток (после потребления всех элементов). Использует BiConsumer.

3. Добавлен класс java.util.stream.Gatherers, который содержит несколько стандартных реализаций Gatherer:

  • fold — подобная reduce операция;
  • mapConcurrentmap с использованием Virtual Threads;
  • scan — инкрементальная аккумулирующая операция;
  • windowFixed — стандартная реализация Fixed Window;
  • windowSliding — стандартная реализация Sliding Window.

Sliding Window, или метод скользящего окна, подразумевает создание окна или диапазона размера N на входных данных и затем движение этого окна (смещение). Это легко показать на простой коллекции чисел (окно размера 3):

Результатом такой операции станет Stream, содержащий все выделенные синей рамкой подколлекции.

Теперь с использованием Gatherers мы можем вывести все подколлекции следующим образом:

public static void main(String[] args) {
    var list = List.of(
        "1", "2", "3", "4", "5", "6", "7", 
        "8", "9", "10", "11", "12", "13", "14"
    );
    int k = 3;

    list.stream()
        .gather(Gatherers.windowSliding(k))
        .forEach(sublist -> System.out.printf("%s ", sublist));
    System.out.println();
}

Получаем вывод:

[1, 2, 3] [2, 3, 4] [3, 4, 5] [4, 5, 6] [5, 6, 7] [6, 7, 8] [7, 8, 9] [8, 9, 10] [9, 10, 11] [10, 11, 12] [11, 12, 13] [12, 13, 14]

Fixed Window имеет крайне схожую реализацию, но теперь смещение равно размеру окна:

Использование идентичное:

public static void main(String[] args) {
    var list = List.of(
        "1", "2", "3", "4", "5", "6", "7",
        "8", "9", "10", "11", "12", "13", "14"
    );
    int k = 3;

    list.stream()
        .gather(Gatherers.windowFixed(k))
        .forEach(sublist -> System.out.printf("%s ", sublist));
    System.out.println();
}

Вывод:

[1, 2, 3] [4, 5, 6] [7, 8, 9] [10, 11, 12] [13, 14]

Помимо создания собственных классов, реализующих Gatherer, есть статические фабричные методы:

  • Gatherer.of(integrator)
  • Gatherer.ofSequential(integrator)

Оба метода имеют вариации с различными дополнительными аргументами в виде функциональных интерфейсов, список которых был упомянут ранее (initializer, integrator, combiner, finisher).

JEP 484: Class-File API

Создание собственного API для работы с class-файлами было предложено ещё в JDK 22. Теперь в версии JDK 24 этот API финализируется.

Активная разработка Java в последние годы привела к появлению частых и регулярных изменений в байткоде, с которым взаимодействуют стандартные инструменты jlink, jar и другие. Для этого взаимодействия они используют библиотеки вроде ASM. Из-за этого для поддержки новых версий байткода инструменты должны дожидаться поддержки библиотеками, которые, в свою очередь, ждут финализации версии JDK. Подобные зависимости сильно замедляют процесс разработки и добавления новых возможностей в class-файлы.

Этот API едва ли найдёт применение для большинства разработчиков, но различные фреймворки и библиотеки (в том числе Spring, Hibernate) работают с байткодом и используют ASM. А старая версия ASM не может работать с новыми версиями JDK. При необходимости обновить в проекте версию JDK придётся обновить и ASM. А для обновления ASM нужно апдейтить всё, что от него зависит. Вот и получается, что обновить придётся практически всё... а хотелось просто поднять версию JDK.

Посмотрим, как выглядит новый API. Немного покопавшись в нём, получилось сделать простой пример чтения статических константных примитивных полей (потребуется базовое понимание структуры class-файлов):

public class ClassFileExample {

  public static void main(String[] args) throws IOException {
    var classFile = ClassFile.of().parse(Path.of("./Main.class"));

    for (var field : classFile.fields()) {
      var flags = field.flags();
      if (flags.has(AccessFlag.STATIC) && flags.has(AccessFlag.FINAL)) {
        System.out.printf("static final field %s = ", field.fieldName());
        var value = field.attributes().stream()
          .filter(ConstantValueAttribute.class::isInstance)
          .map(ConstantValueAttribute.class::cast)
          .findFirst()
          .map(constant -> constant.constant().constantValue().toString())
          .orElse("null");
        System.out.printf("%s%n", value);
      }
    }
  }
}

Решение подобной задачи, внезапно, имеет практическое применение, ведь использование рефлексии приводит к инициализации класса. Возможно, однажды я расскажу о последствиях такой непредумышленной инициализации.

А знакомые с ASM разработчики могут заметить, что авторы решили не использовать паттерн Visitor. Это обосновывается нововведениями Java, в частности pattern matching.

JEP 483: Ahead-of-Time Class Loading & Linking

Целью нововведения является улучшение времени запуска приложений. Для этого добавляется возможность сохранения кэша загруженных классов. Генерация и использование кэша осуществляются в три шага:

1. Генерация конфигурации AOT. Для этого необходимо запустить приложение с флагом -XX:AOTMode=record, и указать путь до итогового файла через -XX:AOTConfiguration=PATH:

java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -jar app.jar

2. Генерация самого кэша с использованием конфигурации. Режим AOT меняется на create, а также добавляется флаг с указанием пути вывода кэша -XX:AOTCache=PATH:

java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar app.jar

3. Запуск приложения с использованием кэша. Для этого оставляем лишь флаг -XX:AOTCache=PATH:

java -XX:AOTCache=app.aot -jar app.jar

В тексте JEP указывают, что время запуска простой программы с использованием Stream API уменьшилось с 0.031 секунд до 0.018 (разница 42%). Время запуска проекта на Spring (Spring PetClinic) уменьшилось с 4.486 до 2.604 секунд.

Я посмотрел разницу во времени запуска простого приложения с использованием Quarkus на примере проекта из недавно опубликованной книги "Quarkus in Action" (GitHub). Время запуска снизилось с 3.480 до 2.328 секунд, т.е. на 39.67%.

JEP 491: Synchronize Virtual Threads without Pinning

Этот JEP решает проблему блокировки платформенных потоков при использовании виртуальных потоков в synchronized блоках. Для понимания изменения необходимо ознакомиться с Project Loom, а конкретно — с виртуальными потоками.

Ещё при введении виртуальных потоков (JEP 444) указывалось два случая, при которых они не освобождают несущий их платформенный поток в случае блокировки:

  • Блокировка происходит в synchronized блоке;
  • Блокировка происходит в нативных методах (будь это JNI или Foreign Functions).

Отныне первый случай не является действительным. Теперь разработчики могут свободно выбирать между использованием ключевого слова synchronized и пакета java.util.concurrent.locks, ориентируясь лишь на требования решаемой задачи.

JEP 490: ZGC: Remove the Non-Generational Mode

Z Garbage Collector (ZGC) поддерживал два режима: Generational и Non-Generational. Поскольку Generational ZGC является лучшей опцией в большинстве случаев, было принято решение упростить дальнейшую поддержку ZGC, отключив один из режимов — Non-Generational. Теперь флаг ZGenerational является устаревшим, и при его использовании будет выведено соответствующее сообщение:

JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe

При использовании методов из sun.misc.Unsafe, связанных с памятью, будет выдаваться предупреждение. Этот функционал был заменён VarHandle API и Foreign Function & Memory API, а само изменение приближает удаление связанных с памятью методов из sun.misc.Unsafe (которые уже были помечены как Deprecated for Removal) и мотивирует разработчиков библиотек переходить на использование новых альтернатив.

JEP 472: Prepare to Restrict the Use of JNI

Использование Java Native Interface (JNI) и Foreign Function & Memory (FFM) теперь приводит к предупреждению:

Это первый шаг к ограничению использования JNI и FFM: в будущем планируется выбрасывать исключение. Это делается не для удаления возможности использования JNI или FFM (что было бы очень иронично, ведь последний вышел в релиз в Java 22), а чтобы соблюсти политику целостности по умолчанию (the policy of integrity by default). По факту это означает лишь то, что разработчики должны явно указывать, что согласны на использование небезопасных возможностей JDK (нативный код).

JEP 493: Linking Run-Time Images without JMOD

Использование флага ‑‑enable-linkable-runtime при сборке JDK позволяет jlink создавать образы без использования JMOD файлов из JDK. Это сокращает итоговый размер образа на 25%.

JMOD-файлы появились ещё в Project Jigsaw, Java 9. Они используются в опциональной фазе линкования при использовании jlink, т.е. при создании оптимизированного по занимаемому месту JRE.

Эти файлы выступают как альтернатива jar, позволяют хранить не только .class файлы и ресурсы, но и нативные библиотеки, лицензии, исполняемые файлы. Всё это затем попадает в итоговый JRE. Поскольку при разработке стандартного приложения файлов JAR более чем достаточно, то использовать JMOD и не приходится. А ещё для этого формата катастрофически мало документации.

Хотя это и не затрагивает непосредственно разработчиков, но однозначно имеет эффект при работе с контейнерами или при необходимости создания минимальных образов. Однако такая сборка не включена изначально, и отдельные провайдеры JDK сами принимают решение об использовании этой оптимизации.

Например, в Eclipse Temurin уже начали использовать этот флаг. Поддержку такой сборки затем добавили и в GraalVM.

JEP 486: Permanently Disable the Security Manager

Подготовка к отключению java.lang.SecurityManager началась ещё в Java 17, когда он был помечен как Deprecated for Removal.

Это связано с крайне редким использованием этого класса при высоких затратах на его поддержание. Теперь к изменениям.

Флаг -Djava.security.manager в любых вариациях более не поддерживается и выдаёт ошибку:

Исключением является -Djava.security.manager=disallow, а использование System::setSecurityManager приведёт к исключению UnsupportedOperationException.

Помимо этого, системные свойства, связанные с SecurityManager отныне игнорируются, а также удалён файл conf/security/java.policy.

Прочие изменения связаны с документацией: удаление упоминаний SecurityManager и SecurityException.

Стоит отметить, что сами классы и методы не удалены, а деградированы до "пустышек ", т.е. они либо возвращают null, false, либо сразу исполняют запрос, либо бросают SecurityException или UnsupportedOperationException.

JEP 479: Remove the Windows 32-bit x86 Port

Поддержка Windows 32-bit x86 наконец прекращается. Это упрощает инфраструктуру сборки и тестирования, а также позволяет перестать направлять ресурсы на поддержание этой платформы.

Одной из причин удаления этого порта является отсутствие поддержки virtual threads, которые откатываются к классическим kernel threads. Помимо этого, поддержка последней 32-битной версии Windows 10 завершается в октябре 2025 года.

JEP 501: Deprecate the 32-bit x86 Port for Removal

Судьба остальных 32-битных платформ также очевидна: их удалят, но не в этом релизе.

Так случилось, что последней 32-битной поддерживаемой платформой является Linux. Для сборки 32-битной версии сейчас потребуется добавление флага ‑‑enable-deprecated-ports=yes:

bash ./configure –enable-deprecated-ports=yes

Однако полное удаление этого порта ожидается уже в Java 25.

JEP 496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism

Этот и следующий JEP относятся к постквантовой криптографии.

Постквантовая криптография относится к созданию криптографических алгоритмов, которые будут эффективны даже после появления квантовых компьютеров.

Добавлена реализация ML-KEM для KeyPairGenerator, KEM, KeyFactory APIs, а именно ML-KEM-512, ML-KEM-768, ML-KEM-1024 согласно стандарту FIPS 203. Так, создание пары ключей можно осуществить следующим образом:

KeyPairGenerator generator = KeyPairGenerator.getInstance("ML-KEM-1024");
KeyPair keyPair = generator.generateKeyPair();

JEP 497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm

В продолжение предыдущего JEP.

Добавлена реализация ML-DSA для KeyPairGenerator, Signature, KeyFactory APIs, а именно ML-DSA-44, ML-DSA-65, ML-DSA-87 согласно стандарту FIPS 204. Подобно предыдущему пункту, посмотрим пример получения соответствующей подписи:

Signature signature = Signature.getInstance("ML-DSA");

JEP 475: Late Barrier Expansion for G1

Напоследок JEP, который не затрагивает напрямую разработчиков Java, это изменения в Garbage-first (G1) сборщике мусора, переносящие реализацию его барьеров на более поздний этап C2 JIT компиляции. Это должно помочь упростить понимание этих барьеров для будущих разработчиков, а также уменьшить время исполнения компиляции C2.

In Preview

Помимо этого списка нововведений часть изменений все ещё находятся состоянии Preview или Experimental:

Рассчитываем, что в одном из будущих (и скорых) релизов получится рассмотреть и показать на практике эти нововведения.

Заключение

Полный список ссылок JEP для подробного изучения можно прочитать по ссылке.

Java продолжает развиваться активными темпами, а появление Class-File API уменьшает количество зависимостей, что ещё сильнее увеличивает скорость обновлений на всей платформе. Но на этом пока завершается релиз Java 24. Значит, можно возвращаться к долгому ожиданию Project Valhalla.

Последние статьи:

Опрос:

Дарим
электронную книгу
за подписку!

book terrible tips
Популярные статьи по теме


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

Следующие комментарии next comments
close comment form
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Я хочу принять участие в тестировании
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте, отфильтровано ли письмо в одну из следующих стандартных папок:

  • Промоакции
  • Оповещения
  • Спам