为什么 Java 8 Optional 是作为最终版本实现的,没有“部分”和“无”层次结构?问题总结适用于 Java 的值类型那么这与什么关系呢?Optional为什么我们需要Java中的值类型?实现值类型

2022-09-02 04:13:28

在 Java 中,实现为 和 的密封层次结构,而不是密封的层次结构。Optionalpublic 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));
    }
}

它们不仅丑陋,而且如果你有一个更长的方法链,则需要在每次调用期间进行评估,即使从一开始就是空的。isPresentOptional

如果我们能够将固定的实现传递到链中,则可以避免。

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

问题

为什么不使用子类型对空事例和非空事例进行建模?


我不是在具体问为什么是 final,而是为什么它没有像许多其他语言那样用 and 实现,所以为什么可选声明为最终类并没有真正的帮助。OptionalSomeNone


答案 1

总结

上的修饰符是为一个更大的功能做准备的:值类型这是Valhalla项目的目标(Java 10 +的功能)。finalclass Optional

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

适用于 Java 的值类型


JEP 169 起草了在 Java 中实现值对象的建议。

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

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

基元类的设计和实现已经足够成熟,我们可以自信地预期在将来的发行版中迁移 Java 平台的某些类,使其成为基元类


那么这与什么关系呢?Optional

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

实际上,似乎每个值类型的盒装形式都是基于值的类。

Optional是基于值的类


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

在 Java 中,从引用类型实例化的对象具有标识。这允许变量引用特定对象并按引用进行比较。所有类/枚举/接口当前都创建引用类型。因此,所有对象都有一个标识

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

对象标识仅用于支持可变性,其中对象的状态可以发生突变,但仍保持相同的固有对象。

标识不是自由的,不可变类型不需要突变,这继承意味着它们不需要标识。

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

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

实现值类型

James Gosling 在 1999 年写了一篇关于将不可变类型编译为值的文章:

在当前的语言规范下,一个足够聪明的优化编译器几乎有可能将某些类转换为未堆分配的轻量级对象,并且按值而不是引用传递:将类及其所有实例变量声明为最终对象

这个想法已经被甲骨文的实验项目Valhalla继承,该项目由Brian Goetz领导。在准备过程中,已经为基于值的类创建了一个规范,其中一个要求是将类设置为 。final

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

值是否可以参与基于继承的子类型化?不可以

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


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

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

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


答案 2

事实上,这个问题并不新鲜。这是Natpryce在他的书中提出的:Growing Object Oriented Software也许更像,但它通过多态性表示2个状态。事实上,它比它应用状态模式只将状态传递到状态一次要快。这意味着如果当前状态为 ,则始终为以下状态。java.util.OptionalOptionalpresentabsentabsentabsent

但我想说另一个场景,即面向对象原理:德米特定律

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

正如你所看到的,LoD原则将避免你编写这样的火车残骸代码,这使得代码紧密耦合和破坏封装,并使其更难更改和维护。

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

然后只有2个额外的检查比也许。一个是中间操作 &,另一个是终端操作,等等。因此,在 中应用状态模式没有太大的优势。OptionalmapflatMaporElseorElseGetOptional


推荐