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

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

有我错过的案子吗?


当前回答

我认为Guava Optional和他们的维基页面说得很好:

Besides the increase in readability that comes from giving null a name, the biggest advantage of Optional is its idiot-proof-ness. It forces you to actively think about the absent case if you want your program to compile at all, since you have to actively unwrap the Optional and address that case. Null makes it disturbingly easy to simply forget things, and though FindBugs helps, we don't think it addresses the issue nearly as well. This is especially relevant when you're returning values that may or may not be "present." You (and others) are far more likely to forget that other.method(a, b) could return a null value than you're likely to forget that a could be null when you're implementing other.method. Returning Optional makes it impossible for callers to forget that case, since they have to unwrap the object themselves for their code to compile. -- (Source: Guava Wiki - Using and Avoiding null - What's the point?)

可选增加了一些开销,但我认为它的明显优势是使其显式化 一个对象可能不存在,它强制程序员处理这种情况。它可以防止有人忘记心爱的人!=空支票。

以2为例,我认为这是更明确的代码:

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

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

对我来说,可选的更好地抓住了没有声卡的事实。

我对你的观点有2个看法:

public Optional<Foo> findFoo(String id); - I am not sure about this. Maybe I would return a Result<Foo> which might be empty or contain a Foo. It is a similar concept, but not really an Optional. public Foo doSomething(String id, Optional<Bar> barOptional); - I would prefer @Nullable and a findbugs check, as in Peter Lawrey's answer - see also this discussion. Your book example - I am not sure if I would use the Optional internally, that might depend on the complexity. For the "API" of a book, I would use an Optional<Index> getIndex() to explicitly indicate that the book might not have an index. I would not use it in collections, rather not allowing null values in collections

一般来说,我会尽量减少传递null值。(一旦烧…) 我认为有必要找到适当的抽象,并向程序员同事指出某个返回值实际代表什么。

其他回答

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

到处使用Optional

在一般情况下

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

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

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

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

关于你的问题

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

优势

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

澄清意图

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

消除不确定性

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

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

更多空检查

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

缺点

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

性能

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

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

序列化

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

不变性

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

我认为Guava Optional和他们的维基页面说得很好:

Besides the increase in readability that comes from giving null a name, the biggest advantage of Optional is its idiot-proof-ness. It forces you to actively think about the absent case if you want your program to compile at all, since you have to actively unwrap the Optional and address that case. Null makes it disturbingly easy to simply forget things, and though FindBugs helps, we don't think it addresses the issue nearly as well. This is especially relevant when you're returning values that may or may not be "present." You (and others) are far more likely to forget that other.method(a, b) could return a null value than you're likely to forget that a could be null when you're implementing other.method. Returning Optional makes it impossible for callers to forget that case, since they have to unwrap the object themselves for their code to compile. -- (Source: Guava Wiki - Using and Avoiding null - What's the point?)

可选增加了一些开销,但我认为它的明显优势是使其显式化 一个对象可能不存在,它强制程序员处理这种情况。它可以防止有人忘记心爱的人!=空支票。

以2为例,我认为这是更明确的代码:

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

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

对我来说,可选的更好地抓住了没有声卡的事实。

我对你的观点有2个看法:

public Optional<Foo> findFoo(String id); - I am not sure about this. Maybe I would return a Result<Foo> which might be empty or contain a Foo. It is a similar concept, but not really an Optional. public Foo doSomething(String id, Optional<Bar> barOptional); - I would prefer @Nullable and a findbugs check, as in Peter Lawrey's answer - see also this discussion. Your book example - I am not sure if I would use the Optional internally, that might depend on the complexity. For the "API" of a book, I would use an Optional<Index> getIndex() to explicitly indicate that the book might not have an index. I would not use it in collections, rather not allowing null values in collections

一般来说,我会尽量减少传递null值。(一旦烧…) 我认为有必要找到适当的抽象,并向程序员同事指出某个返回值实际代表什么。

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

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

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

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

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

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

下面是一些你可以在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).