为什么 Java 21 的 EnumSet 不实现新的 SequencedSet 接口?

Tra*_*rer 5 java collections enumset java-21 sequencedset

Java 21引入有序集合时,我非常高兴开始使用它们。但后来我很快发现JDK的各个角落似乎都会自然地应用新的排序接口,但它们并没有应用。我找不到对此的任何确认,也找不到任何证据表明未来有计划进一步将排序集合接口的使用扩展到更多 JDK 中。这让我感到惊讶,因为 JDK 的作者通常倾向于保持事物的一致性和完整性。

一个具体的例子是EnumSet。据我所知,没有根本原因EnumSet不能/不应该实施SequencedSet. 难道只是没有人愿意付出努力来实现一种reversed()方法吗EnumSet?或者有什么更严重的问题阻止了它?

Stu*_*rks 13

有些人已经挖掘出与这个问题相关的 OpenJDK 工件,即增强请求JDK-6278287和此电子邮件线程。这是该主题的完整讨论。

基础知识

首先,EnumSet 无法实现 SequencedSet 甚至 NavigableSet 并没有根本原因。(对于实现 SequencedMap 或 NavigableMap 的 EnumMap 也类似。此后我将只讨论 Set 类型,但讨论也主要适用于相应的 Map 类型。)

枚举类型具有已定义的顺序,因此 EnumSet 中存在的枚举将始终具有已定义的遇到顺序。EnumSet 的迭代器已定义为按顺序迭代它们。添加对第一个和最后一个元素进行操作并提供反向视图的 SequencedSet 操作是非常明智的。

一个有趣的问题是反转视图的类型应该是什么。明显的类型是SequencedSet<E extends Enum<E>>. 这会起作用,但是这会从返回类型中删除“EnumSet”。EnumSet 不会在 Set 接口上添加任何新的实例方法,但 EnumSet 在 API 中的某些地方使用,例如 EnumSet::complementOf。另外,一些EnumSet方法实现检查参数类型,例如,它们instanceof RegularEnumSet针对这种情况使用并选择快速路径实现。如果反向视图包装器未实现 EnumSet,则该信息将会丢失。(好吧,这些实现可以通过额外的instanceof检查进行改造,以适应相反的观点。)

另一种方法是以某种方式修改 EnumSet 的规范,以便允许迭代按相反的顺序进行。这将允许reversed()有 EnumSet 的返回类型。这可以缓解上述问题,但可能会导致依赖于任何 EnumSet 始终按正向顺序迭代的现有代码出现新问题。如果将反向视图传递给这样的代码,则可能会被破坏。

NavigableSet 提供了 SequencedSet 操作的超集。除了访问第一个/最后一个元素和反转视图之外,它还提供了各种获取子集视图的方法(headSet, subSet, tailSet)。考虑到枚举值是完全有序的,这些也是完全合理的。

用处

EnumSet 的主要用例似乎是作为标志数组,作为在 int 或 long 中存储位以及使用按位布尔运算的替代。最常见的使用模式是单独设置和查询这些标志。例如,“该资源可写吗?” 可以通过对 int 使用按位运算或使用 EnumSet.contains() 来回答。EnumSet 相对于位的主要优点是它是类型安全的。添加 SequencedSet 对于这个用例没有任何帮助(但也没有坏处)。

EnumSet 的另一个用例是作为一组命令或函数。电子邮件线程中的一位受访者谈到使用枚举值来表示命令并通过迭代 EnumSet 按顺序处理它们。这是一个不同的用例,但当前可用的前向迭代大多已经支持它。他几乎承认改造 SequencedSet 并没有多大帮助。

我想有人可能想对 EnumSet 的第一个或最后一个元素进行操作,或者以相反的顺序迭代它,但我还没有听说过这样的用例。(当然,这并不意味着它们不存在。如果它们确实存在,我很乐意听到它们的消息。)不过,到目前为止,这似乎并不令人信服。更难以想象 NavigableSet 的各种子集视图的用例。

成本

添加反向视图可能并不是非常困难,但它不仅仅是一句台词。大部分工作涉及添加反向视图,这通常涉及一个相当薄的包装类,该类主要委托给支持实现。可能有两种包装器,一种用于 RegularEnumSet,一种用于 JumboEnumSet。否则,除了一些位调整来获取最后一个位设置并以相反的顺序迭代它们之外,没有太多工作。

此外,向 JDK 添加 API 时还需要进行测试和规范工作。(大多数人往往低估了与此相关的努力。)

NavigableSet 的子集视图需要更多工作。实现子集视图相当繁琐,并且没有“AbstractNavigableSet”可以轻松使用共享实现。

还需要考虑与其他潜在增强功能的交互,例如潜在的不可修改的 EnumSet,它可能还需要 RegularEnumSet 和 JumboEnumSet 的伴随类。添加 SequencedSet 实现和反向视图可能会使将来添加不可修改的实现变得更加困难。这可能不是不可克服的,但它确实增加了复杂性。

完整性

由于枚举是完全有序的,为了完整性,它们的集合不应该实现现有的最佳类型吗?毕竟,净 API 占用空间基本上为零——没有新的 API 被添加到系统中——也没有添加新的概念。

我对此有点同情。当您使用一个系统时,您希望各部分能够正交且可互换地组合在一起。您可以将枚举存储在常规 Collection、SequencedSet 或 NavigableSet 中;但是,如果将枚举存储在专门用于存储枚举的 EnumSet 中,则您将失去那些更强大的接口的可能性。这可能会给用户带来不舒服的权衡。

另一方面,可以以完整性或一致性的名义完成任意数量的事情,而时间和精力实际上是稀缺的。由此可见,我们只能做理想的子集,即使这意味着系统的某些部分不一致或不完整。该子集是如何选择的?这导致我们......

优先事项

这是我们必须做出判断的地方。首先,存在直接的成本效益权衡。EnumSet 实现 SequencedSet 的成本适中,而 NavigableSet 的成本则较高。实现 SequencedSet 的好处似乎相当低,而 NavigableSet 的好处甚至更低。

还有一个我称之为净收益的概念。EnumSet 没有实现 SequencedSet,但是如果您需要 SequencedSet 提供的东西(例如反向视图)怎么办?你可能会写类似的东西

List.copyOf(enumSet).reversed()
Run Code Online (Sandbox Code Playgroud)

它的代码有点多,涉及一些内存分配,一些操作可能效率较低,而且它是副本而不是视图。关键是获得 EnumSet 的反向视图并非不可能;只是有点不方便而且效率有点低。因此,让 EnumSet 实现 SequencedSet 的净收益相当低。

还有机会成本:如果我们这样做,还有什么事情我们不会做?我不会列举我和我的团队正在做的所有事情,但我认为它们都非常重要,并且可能比让 EnumSet 实现 SequencedSet 更重要。

我之前提到的一件事是不可修改的 EnumSet 的可能性:请参阅JDK-5039214。(不可修改的 EnumSet 实际上是不可变的,因为枚举值是不可变的。)鉴于平台强调线程安全和不可修改的数据,这似乎相当重要。与当前的“哑”不可修改包装器相比,这还提供了一些合理的优化。我认为这样做可能比实现 SequencedSet 更重要,尽管我不得不承认也没有人在做这件事。然而,如果我们要在这个领域做一些事情,我们似乎希望在增强它以实现 SequencedSet 之前实现一个不可修改的 EnumSet。

概括

有时,“为什么不这样做?”的答案是这样的。就像,这是一个坏主意,因为……这里的情况并非如此。

这个案例比较典型,属于可能是好主意的一大类,但其净收益似乎低于其他事物。因此,现在尚未对其进行处理,但将来可能会对其进行处理。或者不,取决于未来会发生什么其他事情。


Ste*_*n C 6

为什么 Java 21 不EnumSet实现新SequencedSet接口?

以下是我对评论及其链接来源的总结:

  • 没有(令人信服的)技术论证为什么EnumSet不能或不应该实施SequencedSet

  • 实施reversed()不会有什么大问题。这只是代码。

那么为什么不呢?

好吧……除非像斯图尔特·马克斯这样的人给我们一个明确的答案,否则我们不知道也不可能知道真正的原因是什么!我的猜测是,原因是以下几个因素的组合:

  • 这是一个疏忽,
  • “我们现在没有时间”,或者
  • “我们不知道这是否值得付出努力”。

但为什么并不重要。正如 @Slaw 指出的,有一个 OpenJDK 错误报告,您可以跟踪: https: //bugs.openjdk.org/browse/JDK-6278287。这证明团队已经意识到这个问题,尽管我们无法告诉您是否有计划修复它。

  • @StuartMarks 我很长一段时间都想知道为什么 `EnumSet` 没有实现 `NavigableSet`,直到我从 Eric Lippert 那里找到了这个很好的答案:“[功能一开始是未实现的,只有当人们花精力实现它们时才被实现](https: //ericlippert.com/2012/03/09/why-not-automatically-infer-constraints/#:~:text=I%E2%80%99m%20often%20asked,how%20obviously%20good)”,答案如下很多“为什么”的问题。但(对我来说)更紧迫的是缺乏“不可变”枚举集。使用包装器时的一个问题是“containsAll”不再是有效的“&amp;”操作。 (8认同)
  • 正在努力……:-) (6认同)
  • @Holger 是的,提供不可修改的 EnumSet 可以说比改造 SequencedSet 或 NavigableSet 更重要。 (5认同)