Много интересных фич
Выход 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 стояла всегда перед защищенным (guarded) образцом того же типа. Благодаря этому селектор-выражение может соответствовать нескольким меткам в блоке 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, поскольку апгрейд с нее будет простым и удобным.
Автор: Дмитрий Чуйко