Скрытые классы, запечатанные классы, текстовые блоки, записи и EdDSA: в JDK 15 имеется много ценного.
Как гласит одно из моих любимых выражений, в Java 15 присутствует много богатого шоколадного добра. В новую версию (релиз 15 сентября 2020г.) включены 14 важных изменений (JEP), направленных на совершенствование JDK. В этой статье дается краткий обзор новинок на основе информации, содержащейся в JEP.
14 JEP можно разделить на пять групп. Для более подробного изучения смотрите документацию по каждому из них.
Новый функционал, захватывающий дух:
- JEP 339: алгоритм цифровой подписи на основе кривой Эдвардса (EdDSA)
- JEP 371: скрытые классы
Дополнения к существующему стандартному функционалу Java SE:
- JEP 378: текстовые блоки
- JEP 377: сборщик мусора ZGC
- JEP 379: Shenandoah — сборщик мусора с малым временем паузы
Улучшение устаревшего функционала Java SE:
- JEP 373: переработка устаревшего DatagramSocket API
С нетерпением ждем новинок:
- JEP 360: запечатанные классы (предварительная версия)
- JEP 375: сопоставление с образцом для instanceof (вторая предварительная версия)
- JEP 384: записи (вторая предварительная версия)
- JEP 383: API доступа к внешней памяти (вторая инкубаторная функция)
Удаление и прекращение поддержки:
- JEP 372: удаление Nashorn JavaScript Engine
- JEP 374: отключение и исключение блокировки с резервированием
- JEP 381: удаление портов Solaris и SPARC
- JEP 385: исключение RMI активации для последующего удаления
Новый функционал, захватывающий дух
Алгоритм цифровой подписи на основе кривой Эдвардса (JEP 339). Я буду первым, кто признает, что алгоритм цифровой подписи Edwards-Curve (EdDSA) немного выходит за рамки моих знаний о шифровании. Ладно, я вообще ничего про это не знаю. Однако, этот JEP разработан как платформо-независимая реализация EdDSA с лучшей производительностью, чем существующая реализация на языке C — ECDSA.
В соответствии с документацией JDK, EdDSA — современная схема подписи с эллиптической кривой, которая имеет несколько преимуществ по сравнению с существующими в JDK.
Цели реализации:
- Основная цель этого JEP — реализация схемы подписи так, как это стандартизировано в RFC 8032. При этом новая схема не заменяет ECDSA.
- Разработка платформо-независимой реализации EdDSA с более высокой производительностью, чем существующая реализация ECDSA при той же степени безопасности. Например, EdDSA, использующий Curve25519 при ~126 битах защиты, должен быть таким же быстрым, как ECDSA, использующий кривую secp256r1 при ~128 битах защиты.
- Помимо этого, реализация не будет содержать секретных областей. Эти свойства важны для предотвращения сторонних атак.
Теперь вы знаете больше, чем я. В скором времени ожидайте статью в журнале Java Magazine, объясняющую EdDSA.
Скрытые классы (JEP 371) — это классы, которые не могут быть задействованы непосредственно байт-кодом других классов. Они предназначены для использования фреймворками, которые динамически создают классы во время выполнения и используют их косвенно через механизм рефлексии. Динамически сгенерированные классы могут потребоваться только в течение ограниченного промежутка времени, поэтому сохранение их на время существования статически сгенерированного класса может излишне увеличить объем памяти.
Динамически сгенерированные классы также нельзя обнаружить. Обнаружение таких классов по имени было бы пагубным, поскольку это подрывает их назначение, заключающееся в том, что они являются просто деталью реализации статически сгенерированного класса.
Релиз скрытых классов закладывает основу отказа от использования разработчиками нестандартного API sun.misc.Unsafe::defineAnonymousClass. Oracle намеревается исключить и удалить этот класс в будущем.
Дополнения к существующим стандартам Java SE
Текстовые блоки (JEP 378), пришедшие из Project Amber, после двух предварительных версий в JDK 13 и 14 окончательно зарелизились. Целью их создания стало желание разработчиков уменьшить трудности объявления и использования многострочных строковых литералов в Java.
Текстовые блоки автоматически форматируют строки предсказуемым образом, но если этого недостаточно, разработчик может взять форматирование на себя. Во второй версии предварительного функционала представлены две новые escape-последовательности для управления новыми строками и пробелами. Например, \<line-terminator> явно подавляет вставку символа новой строки.
Раньше, чтобы вывести одну длинную строку текста, вам пришлось бы написать так:
Использование \ упрощает чтение кода:
Управляющая последовательность \s может предотвратить удаление конечных пробелов. Таким образом, следующий текст представляет три строки, каждая из которых состоит ровно из пяти символов (в средней строке, соответствующей зеленому цвету, \s не используется, потому что в ней и так пять символов).
Сборщик мусора ZGC (JEP 377) был представлен в JDK 11 в качестве экспериментального функционала. Теперь он получил официальный статус. ZGC — это параллельный, поддерживающий NUMA, масштабируемый сборщик мусора с малой задержкой при сборке мусора (менее 10 миллисекунд даже в многотерабайтных кучах). Среднее время паузы, согласно тестам Oracle, составляет менее 1 миллисекунды, а максимальное — менее 2 миллисекунд. На рисунке 1 сравнивается параллельный сборщик мусора Java, G1 и ZGC, при этом время паузы ZGC увеличено в 10 раз.
Рисунок 1. Сравнение времени паузы сборщиков мусора
Тем не менее, для многих нагрузок G1 (который по-прежнему используется по умолчанию) может быть немного быстрее, чем ZGC. Кроме того, для очень маленьких куч, таких как те, которые составляют всего несколько сотен мегабайт, G1 также может быть быстрее. Таким образом, вам рекомендуется провести свои собственные тесты со своими нагрузками, чтобы увидеть, какой сборщик мусора использовать.
Важно: поскольку ZGC уже не является экспериментальным (включен в сборку Oracle OpenJDK и в Oracle JDK), вам не нужно больше использовать -XX:+UnlockExperimentalVMOptions для его активации.
Shenandoah (JEP 379) — еще один вариант сборщика мусора, доступный в некоторых сборках OpenJDK. Он был экспериментальным, начиная с JDK 12. Теперь, как и в случае с ZGC, опция XX:+UnlockExperimentalVMOptions не требуется.
Модернизация устаревшего функционала Java SE
Переработка устаревшего DatagramSocket API (JEP 373). Считайте, что это в основном рефакторинг некоторого кода юрского периода, поскольку этот JEP заменяет старые, трудно поддерживаемые реализации API-интерфейсов java.net.DatagramSocket и java.net.MulticastSocket более простыми и современными реализациями, которые легко поддерживать и отлаживать, и которые будут работать с виртуальными потоками Project Loom.
Поскольку существует очень много кода, использующего старый API (c JDK 1.0), устаревшая реализация не будет удалена. Фактически, новое специфичное для JDK системное свойство, jdk.net.usePlainDatagramSocketImpl, настраивает JDK для использования устаревшей реализации, если рефакторинг API вызывает проблемы при регрессионных тестах или в некоторых случаях.
С нетерпением ждем новинок
Запечатанные классы (JEP 360). Первая предварительная версия, созданная в Project Amber. Запечатанные классы и интерфейсы ограничивают их расширение или реализацию другими классами. Почему это важно? Разработчик может захотеть управлять кодом, который отвечает за реализацию определенного класса или интерфейса. Запечатанные классы предоставляют более декларативный способ, чем модификаторы доступа для ограничения использования суперкласса. Например:
Цель запечатывания класса — позволить клиентскому коду понимать, какие подклассы разрешены. В конце концов, могут быть такие случаи использования, когда ожидается, что исходное определение класса (или интерфейса) будет полностью исчерпывающим, и когда разработчик не разрешает его бесконтрольное расширение — только явно указанные классы могут это делать.
Для запечатанных классов существуют некоторые ограничения:
- Запечатанный класс и его разрешенные подклассы должны находиться в одном модуле. Если они объявлены в безымянном модуле, то их необходимо размещать в одном пакете
- Каждый разрешенный подкласс должен напрямую расширять запечатанный класс
- Каждый разрешенный подкласс должен выбрать модификатор, чтобы описать, как он продолжает запечатывание, инициированное его суперклассом — final, sealed или nonsealed (запечатанный класс не может помешать его разрешенным подклассам делать это)
Сопоставление с образцом для instanceof (JEP 375). Вторая предварительная версия — еще одна разработка Project Amber. Первая предварительная версия функционала была в Java 14 и по сравнению с ней нет никаких изменений.
Цель — улучшить Java за счет сопоставления с образцом для оператора instanceof. Это позволяет более кратко и безопасно выразить общую логику в программе, а именно условное извлечение компонентов из объектов. Позвольте мне отослать вас к отличной статье Мала Гупта «Сопоставление с образцом для instanceof в Java 14» в качестве примера.
Записи (JEP 384), вторая предварительная версия. Записи — это классы, которые действуют как прозрачные носители неизменяемых данных. Новый JEP включает уточнения, основанные на отзывах сообщества, и поддерживает несколько новых дополнительных форм локальных классов и интерфейсов. Записи также поступают от Project Amber.
Записи представляют собой объектно-ориентированную конструкцию, которая выражает простую агрегацию значений. Таким образом, они помогают программистам сосредоточиться на моделировании неизменяемых данных, а не на расширяемом поведении. Записи автоматически реализуют методы, управляемые данными, такие как equals и методы доступа. Записи также сохраняют давние принципы Java, такие как номинальная типизация и совместимость миграции. Другими словами, они упрощают кодирование и чтение классов, содержащих неизменяемые данные.
Доступ к внешней памяти (JEP 383) — это вторая инкубаторная версия API, которая позволяет программам Java безопасно и эффективно обращаться к внешней памяти вне кучи Java. Цель состоит в том, чтобы начать замену java.nio.ByteBuffer и sun.misc.Unsafe. Это часть проекта Panama, который улучшает связь между Java и другими API.
В JEP метко описывается необходимость этого нововведения следующим образом:
Когда дело доходит до доступа к внешней памяти, разработчики сталкиваются с дилеммой — должны ли они выбрать безопасный, но ограниченный (и, возможно, менее эффективный) путь, такой как ByteBuffer API, или они должны отказаться от гарантий безопасности и принять опасный и неподдерживаемый Unsafe API?
Этот JEP представляет безопасный, поддерживаемый и эффективный API для доступа к внешней памяти. Предоставляя целевое решение проблемы доступа к внешней памяти, разработчики будут освобождены от ограничений и опасностей существующих API. Они также получат улучшенную производительность, поскольку новый API будет разрабатываться с нуля с учетом оптимизации JIT.
Удаление и прекращение поддержки
Ни одно из этих решений не должно вызывать споров.
Удаления движка Nashorn JavaScript (JEP 372). Данный движок, его API и инструмент jjs были объявлены устаревшими еще в Java 11. Пришло время попрощаться.
Отключение и исключение блокировки с резервированием (JEP 374) позволяет избавиться от старого метода оптимизации, используемого в JVM HotSpot для уменьшения накладных расходов, связанных с неконтролируемой блокировкой. Данная блокировка исторически приводила к значительному повышению производительности по сравнению с обычными методами блокировки, но прирост производительности, наблюдаемый в прошлом, сегодня гораздо менее очевиден. Стоимость выполнения атомарных инструкций на современных процессорах снизилась.
Блокировка с резервированием привела к появлению большого количества сложного кода, и эта сложность мешает команде разработчиков Java вносить существенные изменения в подсистеме синхронизации. Отключив блокировку, и оставив на усмотрение программиста возможность ее повторного включения, команда разработчиков Java надеется определить, будет ли разумным полностью удалить ее в будущем выпуске.
Удаление портов Solaris и SPARC (JEP 381). В данном случае удалению подлежит весь исходный код, специфичный для операционной системы Solaris и архитектуры SPARC. Больше нечего сказать.
Исключение RMI активации для последующего удаления (JEP 385). Упрощает Java, удаляя устаревшую часть вызова удаленных методов, которая стала опциональной, начиная с Java 8.
Использование активации RMI уменьшается и сокращается. Команда Java не видит свидетельств того, чтобы какие-либо новые приложения были написаны с ее использованием, и есть свидетельства того, что очень немногие существующие приложения используют активацию RMI. Поиск по различным базам кода с открытыми исходниками почти не выявил упоминания каких-либо API, связанных с ней. Никаких отчетов об ошибках активации RMI, генерируемых извне, не поступало в течение нескольких лет.
Поддержка активации RMI как части платформы Java требует постоянных затрат на обслуживание. Это добавляет сложности для RMI. Активацию RMI можно удалить, не затрагивая остальную часть RMI. Ее удаление не снижает ценности Java для разработчиков, но снижает затраты на долгосрочное обслуживание JDK.
Заключение
Java 15 продолжает шестимесячный цикл выпуска новых версий и представляет собой среднего размера релиз, полезный для большинства разработчиков.
Un_ka
Текстовые блоки, конечно прорыв. Как их собираются подсвечивать? В примере видно, что подсветка синтаксиса не та (подсвечено do).
topjava Автор
это недостаток подсветки в carbon.now.sh, в котором мы оформляли код
sshikov
До функциональности груви (2003) и скалы (2004) и так и не дотянули. Интерполяции нет, raw нет (ну так написано в JEP, я не анализировал глубже). Обещали в следующий раз. То есть это конечно приятная новость, но на прорыв как-то не очень тянет.
topjava Автор
Как написано в одной из статей про текстовые блоки:
Java не является первопроходцем в реализации Текстовых блоков. В данном случае она выступает в роли догоняющего, т.к. многострочные строки давно реализованы во многих языках. В статье www.infoq.com/articles/java-text-blocks есть табличка с перечислением языков, которые так или иначе поддерживают разные виды строковых летиралов.
sshikov
Так я вам об этом и говорю — что догонять вполне можно было более хорошее решение, с интерполяцией например, потому что они уже давно в наличии. Уж я не помню, была ли интерполяция в груви с самого начала, но уж лет 10 точно как есть.
Я понимаю при этом, что не всякое решение можно сделать не нарушив совместимость (в том же груви эти строки с интерполяцией — это не String, это GString, и это может что-то поломать).
topjava Автор
В JEP 378: Text Blocks написано:
интерполяция и Raw-строки — это, видимо, вопрос будущего. Ждем)
sshikov
>Обещали в следующий раз.
Я про это сразу и написал.
0xd34df00d
А в скале это в ядре языка или библиотекой сделано?
sshikov
Ну там не так все однозначно.
Если говорить про интерполяцию, то это выглядит в библиотеке как StringContext, где места для подстановки сами по себе (в виде массива кусков строки), а подставляемые параметры — сами по себе. То есть, без поддержки компилятора это совсем не так удобно, как с ним.
lgorSL
Три интерполятора есть из коробки в стандартной библиотеке языка, при желании можно дописать свои. Например, в cats вместо .toString предлагают использовать тайпкласс Show, и в этой библиотеке есть свой интерполятор, который для подставленных в строку элементов будет использовать show.
Единственный недостаток интерполяторов — на вход приходят кусочки строки и подставленные объекты, и доступа к синтаксическому дереву нет. Из-за этого не получится, например, сделать интерполятор, который бы мог подставить "имя переменной = значение".