Java 18. Что нового добавили и почему вам это пригодится

dfdc1d458caeb265cec81297390b0032.png

Много интересных фич

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

Важно понимать, что версии, которые выходят между LTS-релизами, в частности помогают опробовать новые возможности и посмотреть, зайдут ли они комьюнити. Бизнесы, если решат обновляться, будут чаще всего ждать полноценной многолетней поддержки. Поэтому в 18-й версии будет несколько JEP в виде превью и инкубаторов, которые разработчики будут тестировать и давать обратную связь. И уже в готовом виде эти улучшения войдут в 22-ю версию Java.

Сейчас у нас есть возможность попробовать новый функционал и понять, насколько он полезен. А пробовать есть что!

В этой статье мы рассмотрим новые JEP и объясним, что они делают, и для чего они нужны. 

Новые фичи в Java 18

JEP 400: Использование UTF-8 по умолчанию

Что это такое: Многие API (включая стандартные Java API) используют в приложениях так называемый «набор символов по умолчанию». Какой именно 一 зависит от платформы, будь это Windows, Linux, Mac и другие ОС. Более того, та же кодировка использовалась для сохранения исходных .java файлов .

С 18-й Java по умолчанию всегда используется UTF-8.

Зачем это нужно: В некоторых случаях (особенно, когда это касалось азиатских языков и иероглифов), одно и то же приложение, запущенное разными пользователями (даже на одной машине), могло привести к искажению текста. Если виртуальная машина считает кодировкой «по умолчанию» UTF-8, а программа написана в, например, UTF-16, то это может превратить выдаваемый текст в абракадабру, или даже повлиять на работу компилятора javac. 

Об этом, кстати, стоит помнить при миграции с ранних версий Java™, которая, возможно, потребует перекодировки исходных файлов.

Но начиная с 18-й версии эта проблема исчезнет. А при необходимости всегда есть возможность установить другую кодировку по умолчанию вручную.

JEP 408: Простой Web Server

Что это такое: В Java появится легковесный веб-сервер, который можно будет запустить с помощью простой команды $ jwebserver

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

JEP 413: Кусочки кода в документации Java API

Что это такое: Добавляется новый тег @snippet, который используется в комментариях к коду. Если пометить им часть комментария, то компилятор будет валидировать его и оформлять как если бы это был реальный код.

Зачем это нужно: Для вашего удобства! Код в комментариях может содержать кучу ошибок, связанных с человеческим фактором 一 в первую очередь, опечатками. Конечно, компилятор не сможет превратить плохой код в хороший, но про отсутствующую скобку напомнит. А хорошие читаемые комментарии могут сэкономить часы работы при анализе чужого, да и своего кода. 

JEP 416: Новая реализация Core Reflection через MethodHandle

Что это такое: Механизм рефлексии переписан поверх MethodHandle-ов, которые заменяют генерацию байткода для Method: invoke, Constructor: newInstance, Field: get и Field: set. 

Зачем это нужно: Для безопасности и скорости, а также разработки project Valhalla. До этого использовалось три механизма java.lang.reflect API: нативные методы виртуальной машины, динамически генерируемые байткод-стабы в сочетании с доступом к полям через Unsafe, и, собственно, method handles. Такое разнообразие сложно поддерживать при добавлении новых фич, таких как примитивные классы и дженерики с поддержкой примитивов.

Теперь механизм с использованием нативных методов используется только на раннем этапе запуска виртуальной машины. Для улучшения производительности рефлексии в горячем коде рекомендуется хранение экземпляров Method, Constructor и Field в static final полях, чтобы JIT оптимизировал их как константы.

JEP 417: Vector API (третий инкубатор)

Что это такое: Vector API, представленный в Java 16, помогает существенно повысить производительность приложений в некоторых случаях. Он позволяет вручную писать платформенно-независимые векторные алгоритмы на Java™, когда существующая автоматическая векторизация циклов HotSpot не срабатывает для сложного кода.

Зачем это нужно: Производительность некоторых вычислений в области, например, финансовых операций, машинного обучения и криптографии. Эта версия API 一 в стадии третьего инкубатора, созданного на основе отзывов сообщества. Она содержит новые функции, включая поддержку инструкций ARM Scalable Vector Extension (SVE) и повышенную эффективность векторных операций, принимающих маски на архитектурах, аппаратно поддерживающих маскирование.

JEP 418: Интерфейс сервис-провайдера для разрешения сетевого адреса

Что это такое: Сейчас для разрешения имен хостов в межсетевом протоколе (IP), в Java™ используется API java.net.InetAddress. Он, в свою очередь, использует механизм разрешения адреса через системный вызов ОС. Обычно он работает с файлом hosts в сочетании с DNS (система доменных имен).

Этот JEP внедряет интерфейс поставщика услуг (SPI) и ряд новых классов, чтобы API java.net.InetAddress мог использовать другие механизмы разрешения адреса.

Зачем это нужно: Для тестирования, кастомизации и реализации DNS на клиенте без удержания потока операционной системы. Это позволяет, например, эффективно использовать проект Loom, в котором обработка сетевых запросов можно не будет препятствовать одновременной работе множества виртуальных потоков. 

JEP 419: Foreign Function & Memory API (второй инкубатор)

Что это такое: Улучшение фичи, внедренной в более раннюю версию для замены Java Native Interface (JNI) на более продвинутую модель разработки с использованием чисто Java™ кода. Данный JEP позволит подключать нативные библиотеки, вызвать внешние функции и получать более эффективный доступ к памяти за пределами кучи.

В режиме второй инкубации были добавлены новые носители (carriers), такие как boolean и MemoryAddress, упрощенные API для получения downcall MethodHandle-ов (Java→native) и для управления временными зависимостями областей видимости ресурсов. Представлен обобщенный API разыменования для MemorySegment и MemoryAddress.

Зачем это нужно: Для легкого взаимодействия с библиотеками, написанными на других языках. Становится возможным использовать при таком взаимодействии современные идиомы Java, например параллельные стримы при переносе данных.

JEP 420: Pattern Matching для switch (второй preview)

Что это такое: Еще одно обновление уже имеющейся фичи, которая позволяет проверять выражения на соответствие различным образцам с определенным действием для каждого из них. Ранее эта фича была представлена в режиме preview, а сейчас в нее внесены два улучшения. 

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

static void error(Object o) {
  switch(o) {
    case CharSequence cs ->
      System.out.println("A sequence of length " + cs.length());
    case String s ->	// Error - pattern is dominated by previous pattern
      System.out.println("A string: " + s);
  }
}

Если метка в блоке switch будет перекрываться меткой, поставленной ранее в данном блоке, это приведет к ошибке во время компиляции. Таким образом, в похожих ситуациях вы можете обнаружить плохо написанный код.Второе улучшение заключается в повышении эффективности проверки блоков switch на предмет полноты. В результате проверка будет более точной в запечатанных (sealed) иерархиях, где разрешенный непосредственный наследник лишь расширяет вариант обобщенного (generic) родительского класса с осуществленной подстановкой типов. Другими словами, компилятор не только помогает поддерживать код в упорядоченном состоянии, но и полностью понимает иерархии sealed классов.

Зачем это нужно: Как и прошлый JEP, этот сделает Java более современной. Старый синтаксис, разработанный 20 лет назад, проигрывает по удобству написания кода в сравнении с новыми языками программирования, и разработчики Java устраняют этот разрыв подобными нововведениями.

JEP 421: Объявление функции финализации устаревшей

Что это такое: Скрывается устаревшая функция финализации, которая была представлена еще в Java 1.0. В дальнейшем она будет полностью удалена.

Зачем это нужно: Финализация применялась для устранения проблем утечки внешних ресурсов, больше не используемых приложением.

Автоматическая система управления памятью должна применять сборщик мусора для того, чтобы находить недоступные объекты и освобождать их память.

На практике при работе с объектами, которые работают с ресурсами не только виртуальной машины, но и скажем операционной системы, (например, файловыми дескрипторами или блоками нативной памяти), возникает следующая проблема: при освобождении объекта связанные с ним ресурсы операционной системы не освобождаются автоматически (нужен дополнительный вызов).

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

Для борьбы с этим явлением сначала использовался явный вызов Java-методов, освобождающих внешние ресурсы объектов. Его недостаток — высокая вероятность ошибок. К тому же момент вызова может быть выбран не оптимально. 

Следующим решением стала финализация, целью которой было снять с разработчика задачу гарантированного высвобождения ресурсов. Финализаторы вызываются сборщиком мусора автоматически и гарантированно освобождают ресурсы, относящиеся к неиспользуемым объектам.

На практике финализация столкнулась со множеством проблем в работе. 

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

  • Финализатор может сохранять ссылку на финализируемый объект, а это в свою очередь делает возможным его «воскрешение».

  • Финализатор выполняется всегда, даже когда его работа не требуется, например, при закрытии программы. 

  • Для работы финализаторов используются неконтролируемые потоки, и порядок выполнения финализаторов также не определен.

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

Именно поэтому были разработаны альтернативные инструменты, такие как cleaner-ы с явной регистрацией объектов и интерфейс AutoCloseable в сочетании с оператором try-with-resources. 

Процесс перехода займет немало времени, так как многие нативные библиотеки и сам JDK используют финализаторы, которые необходимо убрать из кода. 

Java 18, есть ли повод обновить рантайм?

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

Стоят ли они того, чтобы обновить свой рантайм до не-LTS версии? В большинстве случаев 一 нет. Но учитывая новый полугодовой цикл выпуска новых версий и скорое окончание поддержки Java 8, на которой до сих пор работает множество приложений, оно помогает смотреть в будущее с оптимизмом. Выход новой LTS-версии станет большим событием, а разработку новых программ имеет смысл начинать уже на 18-й Java, поскольку апгрейд с нее будет простым и удобным.

© Habrahabr.ru