openjdk.ruOpenJDK на русском

JEP 390: Warnings for Value-Based Classes

Предупреждения для value-based-классов

ОтветственныйDan Smith
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск16
Обсуждениеvalhalla dash dev at openjdk dot org
ТрудоёмкостьS
ДлительностьXS
РецензентыBrian Goetz
ОдобренBrian Goetz
Создан2020/07/08 21:39
Обновлён2025/11/19 20:35
Задача8249100

Аннотация

Отнести классы-обёртки примитивных типов к value-based-классам и пометить их конструкторы как Deprecated for Removal (устаревший, будет удалён), из-за чего появятся новые предупреждения об использовании устаревших API. Выдавать предупреждения о некорректных попытках синхронизации на экземплярах любых value-based-классов платформы Java.

Мотивация

Проект Valhalla работает над значительным улучшением модели программирования Java в виде primitive-классов. Такие классы объявляют, что их экземпляры не обладают Identity (идентичность объекта) и допускают встроенное (inline) представление или Flattening (плоское размещение): экземпляры можно свободно копировать между областями памяти и кодировать только значениями их полей.

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

Кандидаты на такой перевод неформально обозначены в спецификациях API как value-based-классы. В общих чертах это означает, что они представляют неизменяемые объекты, Identity которых не важна для поведения класса, и не предоставляют механизмов создания экземпляров, таких как публичные конструкторы, которые гарантируют уникальную Identity при каждом вызове.

Классы-обёртки примитивных типов (java.lang.Integer, java.lang.Double и т. д.) тоже планируется сделать primitive-классами. Эти классы удовлетворяют большинству требований для отнесения к value-based, за исключением того, что у них есть устаревшие (начиная с Java 9) конструкторы public. С некоторыми поправками в определении их тоже можно считать value-based-классами.

Перевод в primitive-классы в целом не затронет клиентов value-based-классов, кроме случаев, когда они нарушают рекомендации по использованию этих классов. В частности, при запуске на будущем выпуске Java, в котором такой перевод уже произошёл:

  1. Экземпляры этих классов, равные друг другу (по equals), могут также считаться идентичными (по ==), из-за чего могут сломаться программы, корректное поведение которых зависит от результата !=.

  2. Попытки создать экземпляры классов-обёрток с помощью new Integer, new Double и т. д. вместо неявной упаковки (boxing) или вызова фабричных методов valueOf будут приводить к LinkageError.

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

Для кого-то эти изменения могут оказаться неудобными, но обойти их просто: если вам нужна Identity, используйте другой класс — часто собственный, но могут подойти и Object или AtomicReference. Преимущества перевода в primitive-классы — более высокая производительность, надёжная семантика равенства, объединение примитивов и классов — вполне оправдают это неудобство.

От (1) уже предостерегает то, что фабричные методы value-based-классов не гарантируют уникальную Identity. Практического способа автоматически обнаруживать программы, которые игнорируют эти спецификации и полагаются на поведение текущей реализации, нет, но мы ожидаем, что такие случаи будут редкими.

От (2) можно предостеречь, пометив конструкторы классов-обёрток как Deprecated for Removal: это усилит предупреждения, которые выдаются при компиляции вызовов этих конструкторов. Значительная часть существующих Java-проектов (возможно, 1 %–10 % из них) вызывает конструкторы классов-обёрток, хотя во многих случаях они рассчитаны только на выпуски Java до 9. Многие популярные проекты с открытым исходным кодом уже отреагировали на предупреждения об устаревании в Java 9 и удалили вызовы конструкторов классов-обёрток из исходного кода, и можно ожидать, что так поступят и многие другие, учитывая большую срочность предупреждений «deprecated for removal». Дополнительные возможности для смягчения этой проблемы описаны в разделе Зависимости.

От (3) можно предостеречь, реализовав предупреждения во время компиляции и во время выполнения, которые сообщат программистам, что их операции синхронизации не будут работать в будущем выпуске.

Описание

Классы-обёртки примитивных типов в java.lang (Byte, Short, Integer, Long, Float, Double, Boolean и Character) отнесены к value-based. Описание value-based-классов обновлено: теперь оно допускает устаревшие конструкторы и фабрики с интернированием и лучше согласуется с требованиями для перевода в primitive-классы (например, value-based-класс не должен наследовать поля экземпляра).

Чтобы предостеречь от неправильного использования экземпляров value-based-классов:

  • Конструкторы классов-обёрток примитивных типов, впервые объявленные устаревшими в Java 9, помечены как Deprecated for Removal. Везде, где эти конструкторы вызываются в исходном коде, javac по умолчанию выдаёт предупреждения removal. Инструментом jdeprscan можно находить использование устаревших API в двоичных файлах.

  • В javac реализована новая категория предупреждений, synchronization, которая выявляет использование оператора synchronized с операндом value-based-класса или типа, все подтипы которого по спецификации являются value-based. Эта категория предупреждений включена по умолчанию, и её можно выбрать вручную с помощью -Xlint:synchronization.

  • В HotSpot реализовано обнаружение во время выполнения monitorenter на экземпляре value-based-класса. С параметром командной строки -XX:DiagnoseSyncOnValueBasedClasses=1 эта операция будет считаться фатальной ошибкой. Параметр командной строки -XX:DiagnoseSyncOnValueBasedClasses=2 включает журналирование как в консоль, так и через события JDK Flight Recorder.

Предупреждения о синхронизации во время компиляции зависят от статической типизации, тогда как предупреждения во время выполнения могут срабатывать и при синхронизации на типах классов и интерфейсов, не являющихся value-based, например Object.

Например:

Double d = 20.0;
synchronized (d) { ... } // javac warning & HotSpot warning
Object o = d;
synchronized (o) { ... } // HotSpot warning

Байт-код monitorexit и методы Object wait, notify и notifyAll всегда выбрасывали IllegalMonitorStateException при вызове вне оператора или метода synchronized. Поэтому предупреждения об этих операциях не нужны.

Как обозначаются value-based-классы

В JDK аннотация @jdk.internal.ValueBased сообщает javac и HotSpot, что класс является value-based или что абстрактный класс или интерфейс требует value-based-подклассов.

@ValueBased применяется к следующим объявлениям в API платформы Java и в JDK:

  • классы-обёртки примитивных типов в java.lang;

  • класс java.lang.Runtime.Version;

  • классы «optional» в java.util: Optional, OptionalInt, OptionalLong и OptionalDouble;

  • многие классы API java.time: Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId, OffsetTime, OffsetDateTime, ZoneOffset, Duration, Period, Year, YearMonth и MonthDay, а также в java.time.chrono: MinguoDate, HijrahDate, JapaneseDate и ThaiBuddhistDate;

  • интерфейс java.lang.ProcessHandle и классы, реализующие его;

  • классы, реализующие фабрики коллекций в java.util: List.of, List.copyOf, Set.of, Set.copyOf, Map.of, Map.copyOf, Map.ofEntries и Map.entry.

Если аннотация применена к абстрактному классу или интерфейсу, она применяется и ко всем его подклассам в JDK.

Некоторые классы и интерфейсы в java.lang.constant и jdk.incubator.foreign заявлялись как value-based, но не удовлетворяют пересмотренным требованиям — например, наследуют поля экземпляра — и поэтому не могут быть переведены в primitive-классы. В этом случае описывать их как value-based-классы больше некорректно, и их спецификации пересмотрены.

Объём изменений

Java SE: этот JEP изменяет Java SE, уточняя спецификации классов-обёрток примитивных типов, существующих value-based-классов, а также связанных с ними интерфейсов и фабричных методов. Кроме того, он помечает конструкторы классов-обёрток примитивных типов как Deprecated for Removal. Он не вносит никаких изменений в спецификации Java Language и Java Virtual Machine.

JDK: в JDK этот JEP также добавляет новые возможности предупреждений и журналирования в javac и HotSpot. Кроме того, он определяет аннотацию @jdk.internal.ValueBased и применяет её к ряду классов JDK.

Альтернативы

Мы могли бы отказаться от перевода этих классов в primitive-классы. Однако, когда мы завершим этот перевод, разработчики получат значительные преимущества, а влияние на разработчиков, зависящих от проблемного поведения, относительно невелико.

Предупреждения об устаревании во время компиляции можно было бы дополнить предупреждениями во время выполнения. Это оставлено для дальнейшей работы (см. ниже) в рамках другого JEP.

Могут существовать и другие классы, которые можно перевести в primitive-классы, включая классы API и классы, генерируемые такими возможностями, как java.lang.invoke.LambdaMetafactory. Этот JEP ограничивается классами-обёртками и классами, уже отнесёнными к value-based. Опять же, дополнительные предупреждения можно ввести в рамках дальнейшей работы.

Зависимости

Для перевода value-based-классов в primitive-классы потребуется достаточно длительный период, в течение которого будут действовать эти предупреждения. Самое главное, JEP о превращении классов-обёрток в primitive-классы не может быть реализован раньше, чем через некоторое число выпусков после завершения этого JEP.

Ещё одно предварительное условие для превращения классов-обёрток в primitive-классы — достаточный набор инструментов, чтобы находить и обходить унаследованные случаи использования их конструкторов. В отдельных JEP будут изучены две последующие возможности:

  • Предупреждения HotSpot во время выполнения об использовании устаревших API, включая конструкторы классов-обёрток. Они дополнят предупреждения, выдаваемые javac и jdeprscan.

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