跳到主要内容

JEP 390:基于值的类的警告

概括

将原始包装类指定为_基于值的,_并弃用其构造函数以进行删除,从而提示新的弃用警告。提供有关在 Java 平台中任何基于值的类的实例上进行不当同步尝试的警告。

动机

Valhalla 项目正在以_原始类_的形式寻求对 Java 编程模型的重大增强。此类类将其实例声明为无身份的,并且能够进行内联或扁平化表示,其中实例可以在内存位置之间自由复制,并仅使用实例字段的值进行编码。

原始类的设计和实现已经足够成熟,我们可以自信地预期将 Java 平台的某些类迁移为未来版本中的原始类。

迁移候选者在 API 规范中被非正式地指定为基于值的类。从广义上讲,这意味着它们对不可变对象进行编码,这些对象的身份对于类的行为并不重要,并且它们不提供实例创建机制,例如公共构造函数,这些机制保证每次调用都有唯一的身份。

原始包装类(java.lang.Integerjava.lang.Double等)也旨在成为原始类。这些类满足被指定为基于值的大多数要求,但它们公开了已弃用的(自 Java 9 起)public构造函数。通过对定义进行一些调整,它们也可以被视为基于值的类。

基于值的类的客户端通常不会受到原始类迁移的影响,除非它们违反了这些类的使用建议。特别是,当在发生迁移的未来 Java 版本上运行时:

  1. 这些类的相等 (per equals) 的实例也可能被视为相同 (per ==),可能会破坏依赖结果来!=实现正确行为的程序。

  2. 尝试使用 、 等创建包装类实例new Integernew Double而不是隐式装箱或调用valueOf工厂方法,将产生LinkageErrors。

  3. 尝试同步这些类的实例将产生异常。

这些更改可能对某些人来说不方便,但解决方法很简单:如果您需要身份,请使用不同的类 - 通常是您自己定义的类,但ObjectAtomicReference也可能合适。迁移到原始类的好处——更好的性能、可靠的相等语义、统一原始类和类——将是值得的。

(1) 已经因为避免在基于值的类的工厂方法中做出有关唯一身份的承诺而受到阻碍。没有一种实用的方法可以自动检测忽略这些规范并依赖当前实现行为的程序,但我们预计这种情况很少见。

我们可以通过弃用要删除的包装类构造函数来阻止 (2),这将放大在编译对这些构造函数的调用时出现的警告。现有 Java 项目的很大一部分(可能是其中的 1%-10%)调用包装类构造函数,尽管在许多情况下它们只打算在 9 版之前的 Java 版本上运行。许多流行的开源项目已经通过从其源代码中删除包装构造函数调用来响应 Java 9 的弃用警告,考虑到“已弃用以删除”警告的紧迫性,我们预计会有更多项目这样做。_依赖性_部分描述了缓解此问题的其他功能。

我们可以通过在编译时和运行时实施警告来阻止(3),以通知程序员他们的同步操作在未来版本中将不起作用。

描述

java.lang( ByteShortIntegerLongFloatDoubleBoolean和)中的原始包装类Character已被指定为基于值的。基于值的类的描述已更新,以允许弃用的构造函数和实习工厂,并更好地符合原始类迁移的要求(例如,基于值的类不应继承任何实例字段)。

为了阻止滥用基于值的类实例:

  • 原始包装类构造函数最初在 Java 9 中已弃用,现已弃用并删除。无论在源代码中调用构造函数,javac默认情况下都会产生removal警告。该jdeprscan工具可用于识别二进制文件中已弃用的 API 的使用情况。

  • javac实现一个新的警告类别 ,synchronization它标识synchronized语句与基于值的类类型或子类型全部指定为基于值的类型的操作数的用法。警告类别默认打开,可以使用 手动选择-Xlint:synchronization

  • monitorenterHotSpot 实现了基于值的类实例上发生的运行时检测。命令行选项-XX:DiagnoseSyncOnValueBasedClasses=1会将操作视为致命错误。命令行选项-XX:DiagnoseSyncOnValueBasedClasses=2将通过控制台和 JDK Flight Recorder 事件打开日志记录。

编译时同步警告依赖于静态类型,而运行时警告可以响应非基于值的类和接口类型(例如Object.

例如:

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

字节monitorexit码和Object方法waitnotify、 和notifyAll总是抛出在语句或方法IllegalMonitorStateException之外调用的 if 。synchronized因此不需要对这些操作发出警告。

识别基于价值的类别

在 JDK 中,@jdk.internal.ValueBased注释用于向javacHotSpot 发出信号,表明某个类是基于值的,或者抽象类或接口需要基于值的子类。

@ValueBased适用于 Java 平台 API 和 JDK 中的以下声明:

  • 中的原始包装类java.lang

  • 班上java.lang.Runtime.Version;

  • 中的“可选”类java.utilOptionalOptionalIntOptionalLongOptionalDouble

  • API中的许多类java.timeInstant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId, OffsetTime, OffsetDateTime, ZoneOffset, Duration, Period, Year, YearMonth, and MonthDay, and , in java.time.chrono: MinguoDate, HijrahDate, JapaneseDate, and ThaiBuddhistDate;

  • 接口java.lang.ProcessHandle及其实现类;

  • 中集合工厂的实现类java.utilList.ofList.copyOfSet.ofSet.copyOfMap.ofMap.copyOfMap.ofEntries、 和Map.entry

无论注释应用于抽象类或接口,它也应用于 JDK 中的所有子类。

java.lang.constant和中的一些类和接口声称jdk.incubator.foreign是基于值的,但不满足修订后的要求(例如,它们继承实例字段),因此无法迁移为原始类。在这种情况下,将它们描述为基于值的类不再合适,并且它们的规范已被修改。

变更范围

Java SE:此 JEP 通过细化原始包装类、现有基于值的类以及相关接口和工厂方法的规范来修改 Java SE。它还不赞成删除原始包装类构造函数。它_不会_对 Java 语言或 Java 虚拟机规范进行任何更改。

JDK:在 JDK 中,此 JEP 还为javacHotSpot 添加了新的警告和日志记录功能。它定义了注释@jdk.internal.ValueBased并将其应用于许多 JDK 类。

备择方案

我们可以放弃将这些类迁移为原始类的努力。然而,当我们完成迁移时,开发人员将享受到显着的好处,并且对依赖有问题行为的开发人员的相对影响很小。

可以用运行时警告来补充编译时弃用警告。这将作为另一个 JEP 的未来工作(见下文)。

可能还有其他类可以迁移为原始类,包括 API 类和由java.lang.invoke.LambdaMetafactory.该 JEP 将其自身限制为包装类和已指定为基于值的类。同样,未来的工作可能会引入额外的警告。

依赖关系

将基于值的类迁移为原始类将需要合理的准备时间并发出这些警告。最重要的是,用于创建包装类原始类的 JEP 必须在该 JEP 完成后发布一定数量的版本才能继续。

使包装类原始类的另一个先决条件是有足够的工具来识别和解决其构造函数的遗留使用。两个后续功能将在单独的 JEP 中进行研究:

  • 针对使用已弃用的 API(包括包装类构造函数)的 HotSpot 运行时警告。这将补充javac和产生的警告jdeprscan

  • 支持执行无法更新包装类构造函数用法的二进制文件的工具。例如,这可能使程序员可以选择重写字节码以使用valueOf工厂方法。