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, в котором такой перевод уже произошёл:
-
Экземпляры этих классов, равные друг другу (по
equals), могут также считаться идентичными (по==), из-за чего могут сломаться программы, корректное поведение которых зависит от результата!=. -
Попытки создать экземпляры классов-обёрток с помощью
new Integer,new Doubleи т. д. вместо неявной упаковки (boxing) или вызова фабричных методовvalueOfбудут приводить кLinkageError. -
Попытки синхронизации на экземплярах этих классов будут приводить к исключениям.
Для кого-то эти изменения могут оказаться неудобными, но обойти их просто: если вам нужна 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.