Java:Optional的可选实现

引言

类java.util.Optional被实现为一个内部处理两个案例的单个不可变的具体类;一个带有元素,一个没有。难道不是更好的选择让可选的是接口,而是有两种不同的实现来实现这个接口吗?毕竟,这就是我们通常被教导以面向对象语言做的事情。在本文中,我们将了解当前可选实施的一些潜在参数。我们还将了解为什么以不同的方式实现流,从而能够从文件或甚至数据库表中获得流。

真正的可选实现

真正的java.util.Optional::get实现如下所示:

public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

如可以看到的,有两个代码路径;一个值是NULL(没有元素,抛出异常),一个值是其他值(返回值返回时)。

可选的可选实施

让我们假装我们将回到一个时间机器,并被任务再次实现可选项。我认为,我们中的许多人可能会提出一个类似于下面的初始解决方案(我已经命名了假设的接口选项,这样我们可以将其与“真实”的解决方案区分开来),其中包括两个不同的实现(这里是EmptyOption和PresentOption):

public interface Option<T> {
    T get();
    boolean isPresent();
    public <U> Option<U> map(Function<? super T, ? extends U> mapper);
    static <T> Option<T> empty() { return (Option<T>) EmptyOption.EMPTY; }
    static <T> Option<T> of(T value) { return new PresentOption<>(value); }
    static <T> Option<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
}
final class EmptyOption<T> implements Option<T> {
    static final EmptyOption<?> EMPTY = new EmptyOption<>();
    private EmptyOption() {}
    @Override public T get() { throw new NoSuchElementException(); }
    @Override public boolean isPresent() { return false; }
    @Override
    public <U> Option<U> map(Function<? super T, ? extends U> mapper) {
        requireNonNull(mapper);
        return (Option<U>) EMPTY;
    }
}
final class PresentOption<T> implements Option<T> {
    private final T value;
    PresentOption(T value) { this.value = requireNonNull(value); }
    @Override public T get() { return value; }
    @Override
    public boolean isPresent() { return true; }
    @Override
    public <U> Option<U> map(Function<? super T, ? extends U> mapper) {
        requireNonNull(mapper);
        return Option.ofNullable(mapper.apply(value));
    }
}

为了简洁起见,只显示了几个方法,但原则仍然是一样的:对于元素存在的情况和不存在的情况,不同的实现。这提供了一个更清晰的代码,也为任何人提供了实现选项的可能性。

分析

我相信,这种类型的解决方案是由JDK团队在构想“可选”时进行评估的,我认为这是一个明智的决定,不选择此解决方案。可选项主要用于“包裹”返回值,以防止NPE和返回原始空值的其他缺点。我还认为,设计目标是使用可选的性能影响应该很少到可忽略不计。在下文中,我在一些论点中推测,选择当前的“可选”实现而不是上述实现。

轮廓污染

JIT编译器按需编译Java字节代码,以提高对字节代码的解释性能。为了有效地做到这一点,JIT编译器能够收集各种已知方法的统计信息。每个方法都可以有一个方法数据对象,它包含有关如何使用该方法的度量,并且一旦JVM认为该方法足够"温暖"(即在某些意义上已经足够调用),就会创建该对象。创建和维护方法数据的过程称为"分析。"“轮廓污染”,当方法在调用之间基本不同时发生,包括但不限于提供交替的非null/null元素和调用不同的多晶型方法(例如,参数是类型T的泛型,调用的方法调用T::等于)。Java的一个基石特性是它能够动态调用方法的能力。因此,当选项::get被调用时,emptyoption::get或presentOption::get最终被调用,这取决于调用时的实现情况。一旦该方法被调用了大约10,000次,JIT编译器就使用该方法数据来创建一个高效编译的代码段,该代码片段以最好的方式执行,给出目前为止收集的统计信息。因此,如果所有的元素都存在(使用presentoption),且代码被编译时,则会突然出现一个EmptyOption,代码必须是"后退出",并采取更缓慢的代码路径。在仅一个最终类别中选择可选方法时,不会有任何其他的可选方法的实现,因此,由于不同的实现,没有配置文件污染。JIT可以做出确定性和合理的快速编译码确定。但是,等待,JVM是否可能在启动时检查所有的类,并确定实际上是否有两个实现类的选项,然后它可以将整个事情搞清楚?不,不。我们随时可以自由添加类,因此无法安全地枚举特定接口的所有可能实现。至少直到我们在Java中拥有真正的密封类。

API污染

如果人们可以自由编写可选的自定义Optional,那么与内置的可选方案相比,这些Optional最有可能受到设计缺陷/偏差的影响。此外,人们很可能会让自己的类型Optional接口可选添加到JIT编译器/Profiler的负担,并因此诱使人们使用非预期的复合类型(e.g. Foo implements Bar, Optional)。此外,Optional是Java的一个整体部分,因此,可以使其有效地发展为包括,也许是内联类和其他新的即将到来的Java特征的dk本身。

Optional Vs. Streams

Optional版本相反,java.util.stream.StreamIntStream这样的专门版本确实是接口。为什么Stream不是像Optional那样是一个具体的单一的最终类呢?流有一组完全不同的需求。流可以从集合或数组中获得,但是有更强大的方法来获取流。可以从文件、套接字、随机生成器,甚至从数据库中的表中获取流。如果流是密封的,这些特性是不可能实现的。SpeedmentStream是一个库的示例,它允许从几乎任何数据库中获取标准Java流。您可以在这里下载SpeedmentStream

结论

Optional是密封的,有很好的理由。Optional的内部实现不那么清晰,但这是一个值得付出的代价,它带来了更好的性能和更清晰的用户代码的好处。流是非密封的接口,任何人都可以实现,可以用于从各种来源(包括文件和数据库表)获取元素。Speedment Stream ORM可用于从数据库表中获取流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值