我已经使用Java 8 6个多月了,我对新的API变化非常满意。我仍然不确定的一个领域是什么时候使用Optional。我似乎在想要在任何地方使用它之间摇摆,有些东西可能是空的,而根本没有。

似乎在很多情况下我都可以使用它,但我不确定它是否会增加好处(可读性/零安全性),还是只会导致额外的开销。

所以,我有几个例子,我对社区对Optional是否有益的想法很感兴趣。

1 -当方法可以返回null时,作为一个公共方法返回类型:

public Optional<Foo> findFoo(String id);

2 -当参数可以为空时,作为方法参数:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 -作为bean的可选成员:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 -收集:

总的来说,我不认为:

List<Optional<Foo>>

添加任何东西-特别是因为一个人可以使用过滤器()删除空值等,但在集合中有任何可选的好用途吗?

有我错过的案子吗?


当前回答

1 -当方法可以返回null时,作为一个公共方法返回类型:

这里有一篇很好的文章展示了用例#1的有用性。这段代码

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

转化为这个

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

通过使用Optional作为各自getter方法的返回值。

其他回答

虽然我来晚了,但无论如何,我还是想多说几句。它们违背了Optional的设计目标,Stuart Marks的回答很好地总结了这一点,但我仍然相信它们的有效性。

到处使用Optional

在一般情况下

我写了一篇关于使用Optional的完整博客文章,但它基本上可以归结为:

在设计类时,尽可能避免可选性 在所有其余的情况下,默认应该使用Optional而不是null 可能会有例外: 局部变量 返回值和参数给私有方法 性能关键代码块(不用猜测,使用分析器)

前两个异常可以减少在Optional中包装和解包装引用的开销。它们的选择使得null对象永远不能合法地将边界从一个实例传递到另一个实例。

注意,这几乎不允许集合中出现可选项,这几乎和null一样糟糕。千万别这么做。;)

关于你的问题

是的。 如果没有重载选项,则是。 如果没有其他方法(子类化、装饰……),可以。 请不!

优势

这样做可以减少代码库中null的存在,但并不能根除它们。但这还不是重点。还有其他重要的优势:

澄清意图

使用Optional清楚地表示变量是可选的。任何代码的读者或API的消费者都会被这样的事实所迷惑,即可能没有任何内容,在访问值之前必须进行检查。

消除不确定性

如果没有Optional,空值的含义是不清楚的。它可以是状态的合法表示(参见Map.get),也可以是缺少初始化或初始化失败等实现错误。

随着Optional的持续使用,这种情况发生了巨大的变化。在这里,null的出现已经表明存在bug。(因为如果允许缺少该值,则会使用Optional。)这使得调试空指针异常变得容易得多,因为这个空指针的含义问题已经得到了回答。

更多空检查

既然没有任何东西可以再为空,这就可以在任何地方强制执行。无论是使用注释、断言还是普通检查,您都不必考虑这个参数或那个返回类型是否可以为空。它不能!

缺点

当然,没有什么灵丹妙药……

性能

将值(尤其是原语)包装到额外的实例中会降低性能。在紧密循环中,这可能会变得明显,甚至更糟。

请注意,编译器可能能够规避可选项的短生命周期的额外引用。在Java 10中,值类型可能会进一步减少或消除惩罚。

序列化

可选的是不可序列化的,但解决方案不是太复杂。

不变性

由于Java中泛型类型的不变性,当实际值类型被推入泛型类型参数时,某些操作变得很麻烦。这里给出了一个例子(参见“参数多态”)。

我不认为Optional是可能返回空值的方法的一般替代品。

基本思想是:一个值的缺失并不意味着它在未来可能是可用的。它是findById(-1)和findById(67)之间的差值。

可选项对于调用者的主要信息是,他可能不指望给定的值,但它可能在某个时间可用。也许它会再次消失,然后再回来一次。这就像一个开/关开关。你可以“选择”打开或关闭灯。但是如果你没有灯可以打开,你就别无选择了。

所以我发现它太乱引入可选的地方,以前可能返回null。我仍然会使用null,但是只在一些受限的地方,比如树的根、惰性初始化和显式查找方法。

在java中,除非你沉迷于函数式编程,否则不要使用它们。

它们没有作为方法参数的位置(我保证有一天会有人传递给你一个空的可选参数,而不仅仅是一个空的可选参数)。

它们对返回值有意义,但它们会让客户端类继续延伸行为构建链。

FP和链在像java这样的命令式语言中几乎没有位置,因为它使调试变得非常困难,而不仅仅是阅读。当你走到这一行时,你无法知道程序的状态和意图;你必须一步一步地找出它(尽管有步骤过滤器,但通常不是你的代码和许多堆栈帧深),你必须添加大量断点,以确保它可以停止在你添加的code/lambda中,而不是简单地走if/else/call琐碎的行。

如果你想要函数式编程,选择java以外的语言,并希望你有调试它的工具。

1 -当方法可以返回null时,作为一个公共方法返回类型:

这是Optional的预期用例,如JDK API文档中所示:

Optional主要用于作为方法返回类型,其中 显然需要表示“没有结果”,并且在其中使用null 很可能造成错误。

Optional表示两种状态之一:

它有一个值(isPresent返回true) 它没有值(isEmpty返回true)

因此,如果您有一个方法返回一些东西或不返回,这就是Optional的理想用例。

这里有一个例子:

Optional<Guitarist> findByLastName(String lastName);

此方法接受一个用于在数据库中搜索实体的参数。有可能不存在这样的实体,因此使用Optional返回类型是一个好主意,因为它迫使调用该方法的人考虑空场景。这减少了出现NullPointerException的机会。

2 -当参数可以为空时,作为方法参数:

尽管在技术上是可能的,但这不是Optional的预期用例。

让我们考虑一下您提议的方法签名:

public Foo doSomething(String id, Optional<Bar> barOptional);

主要的问题是我们可以调用doSomething,其中barOptional有三种状态之一:

a可选值,例如doSomething("123",可选。(新酒吧()) 可选的,例如doSomething("123", Optional.empty()) 例如:doSomething("123", null)

这3种状态需要在方法实现中适当地处理。

更好的解决方案是实现重载方法。

public Foo doSomething(String id);

public Foo doSomething(String id, Bar bar);

这使得API的使用者可以非常清楚地知道调用哪个方法,并且不需要传递null。

3 -作为bean的可选成员:

给定示例Book类:

public class Book {
  private List<Pages> pages;
  private Optional<Index> index;
}

Optional类变量遇到了与上面讨论的Optional方法参数相同的问题。它可以有三种状态之一:存在、空或null。

其他可能的问题包括:

Serializable:如果你实现Serializable并尝试序列化这个类的对象,你会遇到java.io.NotSerializableException,因为Optional不是为这个用例设计的 转换为JSON:当序列化为JSON时,可选字段可能会以不希望的方式映射,例如{"empty":false,"present":true}。 尽管如果您使用流行的Jackson库,它确实为这个问题提供了一个解决方案。

尽管存在这些问题,Oracle还是在2014年发布Java 8 Optional时发布了这篇博文。它包含了对类变量使用Optional的代码示例。

public class Computer {
  private Optional<Soundcard> soundcard;  
  public Optional<Soundcard> getSoundcard() { ... }
  ...
}

在接下来的几年里,开发人员找到了更好的替代方法,比如实现getter方法来创建Optional对象。

public class Book {
    private List<Pages> pages;
    private Index index;
    public Optional<Index> getIndex() {
        return Optional.ofNullable(index);
    }
}

在这里,我们使用ofNullable方法返回一个带有值的Optional,如果index是非空的,或者返回一个空的Optional。

4 -收集:

我同意创建一个可选列表(例如List<Optional<Foo>>)不会添加任何东西。 相反,如果该项目不存在,则不要将其包含在列表中。

这里有一个关于……的有趣用法(我认为)。测试。

我打算对我的一个项目进行大量测试,因此我构建断言;只是有些事我必须核实,有些事我不需要。

因此,我构建了一些东西来断言,并使用断言来验证它们,如下所示:

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

在NodeAssert类中,我这样做:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

这意味着断言只在我想检查标签时才会触发!