我已经使用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的主要设计目标是为函数返回值提供一种方法,以指示没有返回值。请看这个讨论。这允许调用方继续一连串流畅的方法调用。

这与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,但通常情况下,最好避免这样做。

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

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方法的返回值。