我已经使用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时,作为一个公共方法返回类型:

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

其他回答

似乎Optional只有在Optional中的类型T是int、long、char等基本类型时才有用。对于“真正的”类,这对我来说没有意义,因为你可以使用空值。

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

Nullable < T >

在c#中,这个Nullable<T>很久以前就被引入到换行值类型中。

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

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

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

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

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

地图 平面地图 或者其他 orElseThrow ifPresentOrElse 获取

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

(没有)

这实际上是一个苹果和橙子的比较:Optional<T>是一个对象的实际实例(除非它是null…但这可能是一个错误),而null是一个中止的对象。对于null,您所能做的就是检查它实际上是否为空。所以如果你喜欢在对象上使用方法,Optional<T>适合你;如果您喜欢在特殊的字面量上进行分支,那么null是适合您的。

Null不构成。您不能简单地组合一个只能进行分支的值。但是Optional<T>确实作曲。

例如,您可以使用map创建任意长的“如果非空则应用此函数”链。或者,您可以有效地使用ifPresent创建一个命令代码块,如果可选值非空,则使用该可选值。或者你可以通过使用ifPresentOrElse来创建一个“if/else”,如果非空则使用非空可选参数,否则执行其他代码。

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

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

这对某些人来说可能不够好,就风格而言。

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

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

总之,总结一下:Optional<T>是一个非常健壮的空或现对象。Null只是一个哨兵值。

主观上忽视了原因

There seems to be a few people who effectively argue that efficiency should determine whether one should use Optional<T> or branch on the null sentinel value. That seems a bit like making hard and fast rules on when to make objects rather than primitives in the general case. I think it’s a bit ridiculous to use that as the starting point for this discussion when you’re already working in a language where it’s idiomatic to make objects left-and-right, top to bottom, all the time (in my opinion).

就我个人而言,我更喜欢使用IntelliJ的代码检查工具来使用@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,不需要包装和展开值。(或创建包装器对象)

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

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

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

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

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