【问题标题】:Why is Java 8 Optional implemented as final, without Some and None hierarchy?为什么 Java 8 Optional 实现为 final,没有 Some 和 None 层次结构?
【发布时间】:2018-01-13 04:52:32
【问题描述】:

在 Java 中,Optional 实现为public final class Optional<T> { ... },而不是SomeNone 的密封层次结构。

为什么这里不是这样?这是Java中缺少sealed的解决方法吗?有没有更深层次的道理?

如果您查看方法实现,您会发现通过这种方式,它具有丑陋的空检查:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

它们不仅丑陋,而且如果您有更长的方法链,则需要在每次调用期间评估 isPresent,即使 Optional 从一开始就为空。

如果我们可以将一个固定的实现向下传递,它就可以避免。

optional
  .map(i -> i) // isPresent()
  .map(Object::toString) // isPresent()
  .map(String::length) // isPresent()
  .map(...) // isPresent()

问题

为什么不使用子类型来模拟空和非空案例?


我并没有特别问为什么 Optional 是最终的,而是为什么它没有像许多其他语言那样用 SomeNone 实现,所以 Why is optional declared as a final class 是不是很有帮助。

【问题讨论】:

  • 如果 jsr 没有说明这件事,我们只能推测。
  • @VinceEmigh 我不是在问禁止继承。我想知道是什么推动了在不使用任何形式的继承的情况下实现 Optional 的原因——这是 Scala、Haskell 甚至 Java 的 Vavr 库中的标准方法。
  • 这不是没有密封的解决方法。构造函数很可能是私有的,并且 Some 和 None 实现为私有静态嵌套类。我猜使用 if 的实现比基于多态的实现更简单、更快。
  • @JBNizet 这并不能真正说服我。通过这种方式,如果您有 N 个链式 map() 调用,它将评估 isPresent N 次,这可以通过采用 Some/None 方式来避免。
  • Spec for value-based types。 A paper on value-types(计划为 Valhalla)“值可以参与基于继承的子类型化吗?不能。” - “限制或禁止值类型的子类化和子类型化的决定是必要的以避免指针多态。" - 似乎他们只是在坚持他们的计划,可能是将现有的基于值的类转换为值类型。

标签: java java-8 optional


【解决方案1】:

总结

class Optional 上的 final 修饰符是为更大的功能做准备的:value typesProject Valhalla 的目标(Java 10+ 的功能)。

您可以在下面链接的 2014 年文章中阅读有关 Java 值类型的所有信息:

Value Types for Java


JEP 169 起草了在 Java 中实现值对象的提案。

提供用于处理不可变和无引用对象的 JVM 基础架构,以支持使用非原始类型进行高效的按值计算。

JEP 390 暗示了“将某些类迁移为原始类”的可能性:

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


那么这和Optional有什么关系呢?

在 2014 年的论文中,提到了基于值的类如何充当值类型的盒装版本

事实上,每个值类型的装箱形式似乎都将是一个基于值的类。

Optionalvalue-based class


为什么我们需要 Java 中的值类型?

在 Java 中,从引用类型实例化的对象具有 identity。这允许特定对象由变量引用通过引用比较。所有类/枚举/接口当前都创建引用类型。因此,所有对象都有一个身份

但是,理论上,并非所有对象都需要身份,正如上面链接的 2014 年论文中明确提到的那样:

对象标识仅用于支持可变性,其中对象的状态可以改变,但仍然是相同的内在对象。

身份不是免费的,不可变类型不需要突变,这意味着它们不需要身份。

不可变类型的标识导致占用过多:

对象标识具有占用空间和性能成本,这是 Java 与其他许多面向对象语言不同,具有原语的主要原因。

实现值类型

James Gosling 写了一篇关于将不可变类型编译为值的 article back in 1999

根据当前的语言规范,足够优化编译器将某些类转换为轻量级对象几乎是可能的,这些对象不是堆分配的并且通过值而不是引用传递:声明类及其所有实例变量为final

这个想法已被 Oracle 的实验性项目 Valhalla 继承,由 Brian Goetz 领导。在准备中,已经创建了一个specification for value-based classes,其中一个要求是类是final

2014 年关于 Java 值类型的论文进一步揭示了强制执行 final 要求的决定:

值可以参与基于继承的子类型化吗? 没有

值类可以是抽象的还是非最终的? 不能


限制或禁止值类型的子类化和子类型化的决定对于避免指针多态是必要的。

因此,我们可以确保在方法接收器的确切类型中明确解析所有方法。调用一个值方法总是像 invokestatic 或 invokespecial 而不是像 invokevirtual 或 invokeinterface。

值类型不能参与传统的子类型化(如果有的话,它会受到限制)。

【讨论】:

  • 这仍然没有意义,值类型不需要是不可变的...... C# 有可变的值类型
  • @Enerccio 这不是要求,而是偏好。如果一种类型不需要标识,为什么要给它一个标识?这是一种浪费。至于 C# 允许 mutable 值类型,是的,这是可能的,但是frowned upon:“一条建议:不要。可变值类型是一个坏主意,应该避免。 "
【解决方案2】:

其实这个问题并不新鲜。它是由 Natpryce 在他的书中提出的:Growing Object Oriented SoftwareMaybe 更像java.util.Optional,但它通过多态性表示 2 个状态。实际上,它比Optional 更快,因为它只应用State Patternpresent 状态转换为absent 状态一次。这意味着如果当前状态为absent,则以下状态始终为absent

但我想说另一种场景是面向对象的原则:Law of Demeter

  • 每个单位应该对其他单位只有有限的知识:只有与当前单位“密切”相关的单位。
  • 每个单位只应与其朋友交谈;不要与陌生人交谈。
  • 只与您的直接朋友交谈。

如您所见,LoD 原则将避免您编写这样的train wreck 代码,这会使代码紧密耦合和破坏封装,并使其更难更改和维护。

简而言之,如果您根据 LoD 原则,您不应该在程序中编写任何map(one).map(another).map(...) 链调用。从这个角度来看,引入继承层次结构来表示其内部状态没有任何好处。因为State Pattern 如果你引入一个新的状态更难维护并且更难调试。

那么在Optional 中只比Maybe 多检查2 次。一种是中间操作map&flatMap,另一种是终端操作orElseorElseGet和.etc。所以在Optional中应用State Pattern并没有太大的优势。

【讨论】:

  • LoD 关注content coupling,其中一个对象依赖于另一个对象的内部工作,导致代码混乱。在这种情况下,map 不仅返回了一个全新的Optional,而且Optional 的内部工作也没有暴露出来,所以即使map 返回了相同的实例,它也没有什么不同拨打后续电话:opt.map(...); opt.map(...); ...。不要将 fluent interfaces 与 LoD 违规混淆。
  • @VinceEmigh 感谢您的反馈。也许我把你弄糊涂了。我的意思是map(a-&gt; a.b).map(b-&gt;b.c).map(...),它会违反LoD原则,例如:a.b.c...N,:)
  • 这不是Optional#map 的错,而是可选类型的错误是包装。没有什么可以强迫a 暴露b,这是用户的选择。
  • @VinceEmigh 很好,所以我在回答中说过:“另一个场景”。确实,我已经赞成你的答案,我没有反驳你的答案,我只是想描述一下我的想法,所以我再次重新提出这个问题。你谈论价值对象,我谈论 LoD。 :)
猜你喜欢
  • 1970-01-01
  • 2014-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-01
  • 2011-07-05
相关资源
最近更新 更多