我已经使用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的设计目标,Stuart Marks的回答很好地总结了这一点,但我仍然相信它们的有效性。

到处使用Optional

在一般情况下

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

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

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

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

关于你的问题

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

优势

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

澄清意图

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

消除不确定性

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

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

更多空检查

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

缺点

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

性能

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

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

序列化

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

不变性

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

其他回答

Oracle教程:

Optional的目的不是替换代码库中的每一个空引用,而是帮助设计更好的api,使用户可以通过读取方法的签名来判断是否需要一个可选值。此外,Optional强制您主动展开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;
    }
}

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

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

到处使用Optional

在一般情况下

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

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

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

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

关于你的问题

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

优势

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

澄清意图

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

消除不确定性

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

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

更多空检查

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

缺点

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

性能

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

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

序列化

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

不变性

由于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>>)不会添加任何东西。 相反,如果该项目不存在,则不要将其包含在列表中。

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

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

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

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