我已经使用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>>

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

有我错过的案子吗?


当前回答

Optional的主要设计目标是为函数返回值提供一种方法,以指示没有返回值。请看这个讨论。这允许调用方继续一连串流畅的方法调用。

这与OP问题中的用例#1最接近。尽管,缺少值是比null更精确的表述,因为像IntStream。findFirst永远不能返回null。


对于用例#2,将一个可选参数传递给一个方法,这可以实现,但相当笨拙。假设您有一个方法,它接受一个字符串,后面跟着一个可选的第二个字符串。接受Optional作为第二个参数将导致如下代码:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

即使接受null也更好:

foo("bar", "baz");
foo("bar", null);

最好的方法是有一个重载方法,接受单个字符串参数,并为第二个参数提供默认值:

foo("bar", "baz");
foo("bar");

这确实有局限性,但它比上面任何一种都要好得多。

用例#3和#4,在类字段或数据结构中使用Optional,被认为是对API的滥用。首先,它违背了上文所述的Optional的主要设计目标。其次,它不会增加任何价值。

处理Optional中缺少值的方法有三种:提供替代值、调用函数提供替代值或抛出异常。如果你要存储到一个字段中,你会在初始化或赋值时这样做。如果您正在向列表中添加值,如OP所述,您可以选择不添加值,从而“平坦化”缺少的值。

我相信有人会想出一些人为的例子,他们确实想在字段或集合中存储Optional,但通常情况下,最好避免这样做。

其他回答

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

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

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

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;
    }
}

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

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

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

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

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

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

Optional与Iterator设计模式的不可修改实例具有类似的语义:

它可能引用也可能不引用对象(如isPresent()所给出的) 如果它确实引用了一个对象,则可以解除引用(使用get()) 但是它不能被提升到序列中的下一个位置(它没有next()方法)。

因此,在您以前可能考虑使用Java迭代器的情况下,请考虑返回或传递一个Optional。

Oracle教程:

Optional的目的不是替换代码库中的每一个空引用,而是帮助设计更好的api,使用户可以通过读取方法的签名来判断是否需要一个可选值。此外,Optional强制您主动展开Optional以处理缺少值的情况;因此,可以保护代码不受意外空指针异常的影响。

可选类让你避免使用null,并提供一个更好的选择:

这鼓励开发人员检查是否存在,以避免未捕获的NullPointerException。 API有了更好的文档记录,因为它可以看到,在哪里可以期望那些可能缺失的值。

Optional为进一步处理对象提供了方便的API: isPresent ();get ();orElse ();orElseGet ();orElseThrow ();map ();filter ();flatmap()。

此外,许多框架积极地使用这种数据类型并从它们的API中返回它。