用于可选

时间:2014-05-04 10:07:37

标签: java java-8 optional

现在已经使用Java 8超过6个月左右,我对新的API更改感到非常满意。我仍然不自信的一个领域是何时使用Optional。我似乎想要在任何地方使用它可能null,而且无处可去。

在我可以使用它时似乎有很多情况,而且我不确定它是否会增加好处(可读性/无效安全性)或者只会导致额外的开销。

所以,我有一些例子,我会对社区关于Optional是否有益的想法感兴趣。

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

public Optional<Foo> findFoo(String id);

2 - 当参数可能是null时作为方法参数:

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

3 - 作为bean的可选成员:

public class Book {

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

}

4 - 在Collections

总的来说,我不认为:

List<Optional<Foo>>

添加了任何内容 - 特别是因为可以使用filter()删除null值等,但是集合中Optional是否有任何好的用途?

我错过的任何情况?

15 个答案:

答案 0 :(得分:175)

Optional的要点是为函数提供一种返回值的方法,以指示缺少返回值。见this discussion。这允许调用者继续一系列流畅的方法调用。

这与OP的问题中的用例#1 最匹配。虽然缺少值是比 null 更精确的公式,因为像IntStream.findFirst之类的东西永远不会返回null。

对于用例#2 ,将可选参数传递给方法,可以使其工作,但它相当笨拙。假设您有一个方法,它接受一个字符串后跟一个可选的第二个字符串。接受Optional作为第二个arg会产生如下代码:

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的人为案例,但总的来说,最好避免这样做。

答案 1 :(得分:64)

我已经迟到了,但是为了它的价值,我想加上我的2美分。他们反对design goal of OptionalStuart Marks's answer对此进行了很好的总结,但我仍然相信它们的有效性(显然)。

使用Optional Everywhere

一般

我写了一整篇blog post about using Optional,但基本上归结为:

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

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

请注意,这几乎不会允许Optional集合中的null几乎与null一样糟糕。不要这样做。 ;)

关于您的问题

  1. 如果无法选择超载,是的。
  2. 如果其他方法(子类化,装饰......)别无选择,是的。
  3. 请不要!
  4. 优点

    这样可以减少代码库中Optional的存在,尽管它不能消除它们。但这甚至不是主要观点。还有其他重要的优点:

    澄清意图

    使用Optional清楚地表明变量是可选的。您的代码的任何读者或您的API的消费者都会被殴打,因为在访问该值之前可能没有任何内容并且需要进行检查。

    删除不确定性

    如果没有null,则Optional发生的含义尚不清楚。它可能是状态的合法表示(请参阅Map.get)或实现错误,如初始化丢失或失败。

    随着null的持续使用,这种情况发生了巨大变化。这里,Optional的出现已经表明存在错误。 (因为如果允许丢失该值,则会使用null。)这使得调试空指针异常变得更加容易,因为已经回答了null的含义问题。 / p>

    更多空检查

    现在没有任何东西可以Optional了,这可以在任何地方强制执行。无论是注释,断言还是普通检查,您都不必考虑此参数或返回类型是否为null。它不能!

    缺点

    当然,没有银弹......

    性能

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

    请注意,编译器可能会绕过{{1}} s的短暂生命周期的额外引用。在Java 10 value types中可能会进一步减少或消除惩罚。

    序列化

    Optional is not serializableworkaround并不过分复杂。

    不变性

    由于Java中泛型类型的不变性,当将实际值类型推入泛型类型参数时,某些操作会变得很麻烦。给出了一个例子here (see "Parametric polymorphism")

答案 2 :(得分:24)

就个人而言,我更喜欢使用IntelliJ's Code Inspection Tool来使用@NotNull@Nullable检查,因为这些检查主要是编译时间(可以进行一些运行时检查)这在代码可读性方面具有较低的开销和运行时性能。它不像使用Optional那样严格,但是缺乏严谨性应该得到适当的单元测试的支持。

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

这适用于Java 5,无需包装和解包值。 (或创建包装器对象)

答案 3 :(得分:22)

我认为Guava Optional和他们的wiki页面非常适合:

  

除了通过赋予null名称而增加可读性之外,Optional的最大优点是它的白痴证明。如果您希望程序完全编译,它会强制您主动考虑缺席情况,因为您必须主动解包可选并解决该情况。 Null使得简单地忘记事情变得非常容易,尽管FindBugs有所帮助,但我们认为它几乎不会解决这个问题。

     

当您返回可能存在或不存在的值时,这尤其重要。你(和其他人)更有可能忘记other.method(a,b)可以返回一个空值而不是你可能会忘记当你实现other.method时a可能为null。返回Optional使得调用者无法忘记这种情况,因为他们必须自己解包对象以便编译代码。      - (来源:Guava Wiki - Using and Avoiding null - What's the point?

Optional增加了一些开销,但我认为它的明显优势是让它显式 一个对象可能缺席,并强制程序员处理这种情况。它可以防止有人忘记心爱的!= null支票。

2为例,我认为编写代码要明确得多:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

if(soundcard != null){
  System.out.println(soundcard);
}

对我来说,Optional更好地捕捉到没有声卡存在的事实。

关于你的观点我的2¢:

  1. public Optional<Foo> findFoo(String id); - 我不确定这一点。也许我会返回一个Result<Foo>,可能为空或包含Foo。这是一个类似的概念,但不是真正的Optional
  2. public Foo doSomething(String id, Optional<Bar> barOptional); - 我更喜欢@Nullable和findbugs检查,如Peter Lawrey's answer所示 - 另见this discussion
  3. 您的书籍示例 - 我不确定是否会在内部使用Optional,这可能取决于复杂性。对于书籍的“API”,我会使用Optional<Index> getIndex()明确指出该书可能没有索引。
  4. 我不会在集合中使用它,而是不允许集合中的空值
  5. 一般来说,我会尽量减少null的传递。 (一旦烧毁......) 我认为值得找到合适的抽象,并向其他程序员表明某个返回值实际代表什么。

答案 4 :(得分:12)

来自Oracle tutorial

  

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

答案 5 :(得分:4)

  

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

这里是good article,显示了用例#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 方法的返回值。

答案 6 :(得分:3)

答案 7 :(得分:2)

这是一个有趣的用法(我相信)...测试。

我打算对我的一个项目进行大量测试,因此我建立了断言;只有我需要验证的东西和其他我没有验证的东西。

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

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

这意味着如果我想检查标签,断言真的只会触发!

答案 8 :(得分:1)

似乎Optional仅在Optional中的类型T是intlongchar等原始类型时才有用。对于&#34; real&# 34;类,对我来说没有意义,因为你可以使用null值。

我认为这是从这里(或从另一个类似的语言概念)中获取的。

Nullable<T>

在C#中,很久以前就引入了Nullable<T>来包装值类型。

答案 9 :(得分:1)

我认为Optional不能替代可能返回null值的方法。

基本思想是:缺少价值并不意味着它可能在将来可用。这是findById(-1)和findById(67)之间的区别。

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

所以我觉得在任何地方引入Optional可能会返回null之前就太乱了。我仍然会使用null,但仅限于树的根,延迟初始化和显式查找方法等限制区域。

答案 10 :(得分:1)

Optional类可让您避免使用null并提供更好的选择:

  • 这鼓励开发人员检查是否存在,以避免NullPointerException未被捕获。

  • API的文档越来越好,因为可以看到在哪里可以期待不存在的值。

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

此外,许多框架都主动使用此数据类型并从其API返回。

答案 11 :(得分:1)

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

它们没有方法参数的位置(我希望某人将来有一天会为您传递一个null可选内容,而不仅仅是一个空的可选内容。)

对于返回值有意义,但它们邀请客户端类继续扩展行为构建链。

FP和链在诸如Java之类的命令性语言中几乎没有位置,因为它使得调试非常困难,而不仅仅是阅读。当您上线时,您将不知道程序的状态或意图。您必须介入以解决这个问题(进入到通常不是您自己的代码中,并且尽管有阶跃过滤器也进入很多堆栈框架),并且必须添加大量断点以确保其可以停止在您添加的代码/ lambda中,而不是简单地走if / else / call平凡的行。

如果您要进行函数式编程,请选择Java以外的其他东西,并希望您有调试它的工具。

答案 12 :(得分:0)

Optional has similar semantics to an unmodifiable instance of the Iterator design pattern

  • 它可能引用或可能不引用对象(由isPresent()给出)
  • 如果确实引用了对象,则可以取消引用(使用get()
  • 但不能前进到序列中的下一个位置(它没有next()方法)。

因此,请考虑在先前考虑使用Java Iterator的情况下返回或传递Optional

答案 13 :(得分:0)

以下是您可以在Optional<T>实例上执行的一些方法:

  • map
  • flatMap
  • orElse
  • orElseThrow
  • ifPresentOrElse
  • get

以下是您可以在null上执行的所有方法:

  • (没有)

这实际上是苹果与桔子的比较:Optional<T>是对象的实际实例(除非它是null…但这可能是一个错误),而null是中止的对象。 null所能做的就是检查它是否实际上为null。因此,如果您想对对象使用方法,Optional<T>非常适合您;如果您希望使用特殊文字,请使用null

null不组成。您根本无法撰写只能作为分支依据的值。但是Optional<T>确实可以构成。

例如,您可以使用map进行“如果非空则应用此功能”的任意长链。或者,您可以使用ifPresent来有效地编写命令式代码块,以使用可选的非空代码。或者,您可以使用ifPresentOrElse来创建一个“ if / else”,如果它是非空的或者执行一些其他代码,它将消耗非空的可选内容。

…在这一点上,我认为我们遇到了语言的真正局限性:对于非常必要的代码,您必须将它们包装在lambda中并将它们传递给方法:

    opt.ifPresentOrElse(
            string -> { // if present...
                // ...
            }, () -> { // or else...
                // ...
            }
    );

对于某些人来说,这可能不够好。

如果Optional<T>是我们可以进行模式匹配的代数数据类型(这显然是伪代码:

    match (opt) {
        Present(str) => {
            // ...
        }
        Empty =>{
            // ...
        }
    }

但是总而言之,Optional<T>是一个非常健壮的空缺或存在对象。 null只是一个哨兵值。

主观上被忽视的原因

似乎有一些人有效地辩称效率应该决定是使用Optional<T>还是使用null哨兵值的分支。在一般情况下,这似乎有点像在何时创建对象而不是原始对象上制定严格的规则。我认为使用 作为本次讨论的起点有点荒谬,当您已经使用一种习惯于使对象从左到右,从上到下,所有时间(我认为)。

答案 14 :(得分:-2)

Java SE 8引入了一个名为java.util.Optional,

的新类

您可以使用空值创建一个空的Optional或Optional。

Optional<String> emptyOptional = Optional.empty(); 

这是一个带有非空值的Optional:

String valueString = new String("TEST");
Optional<String> optinalValueString = Optional.of(valueString );

如果值存在则执行某些操作

现在您有了一个Optional对象,您可以访问可用的方法来显式处理值的存在与否。而不必记住进行空检查,如下所示:

String nullString = null;
if (nullString != null) {
    System.out.println(nullString);
}

您可以使用ifPresent()方法,如下所示:

Optional<String> optinalString= null;
optinalString.ifPresent(System.out::println);

package optinalTest;

import java.util.Optional;

public class OptionalTest {
    public Optional<String> getOptionalNullString() {
        return null;
//      return Optional.of("TESt");
    }

    public static void main(String[] args) {

        OptionalTest optionalTest = new OptionalTest();

        Optional<Optional<String>> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString());

        if (optionalNullString.isPresent()) {
            System.out.println(optionalNullString.get());
        }
    }
}