用于可选

Wil*_*ill 242 java optional java-8

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

似乎有很多情况我可以使用它,我不知道它是否增加了好处(可读性/无效安全性)或只是导致额外的开销.

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

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

public Optional<Foo> findFoo(String id);
Run Code Online (Sandbox Code Playgroud)

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

public Foo doSomething(String id, Optional<Bar> barOptional);
Run Code Online (Sandbox Code Playgroud)

3 - 作为bean的可选成员:

public class Book {

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

}
Run Code Online (Sandbox Code Playgroud)

4 - 在Collections:

一般来说,我不认为:

List<Optional<Foo>>
Run Code Online (Sandbox Code Playgroud)

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

我错过了什么案例?

Stu*_*rks 189

重点Optional是为函数提供一种返回值的方法,以指示缺少返回值.见这个讨论.这允许调用者继续一系列流畅的方法调用.

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

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

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());
Run Code Online (Sandbox Code Playgroud)

即使接受null也更好:

foo("bar", "baz");
foo("bar", null);
Run Code Online (Sandbox Code Playgroud)

可能最好的方法是使用一个重载方法接受单个字符串参数并为第二个提供默认值:

foo("bar", "baz");
foo("bar");
Run Code Online (Sandbox Code Playgroud)

这确实有局限性,但它比上述任何一个都要好得多.

用例#3#4具有Optional类字段或数据结构,被认为是对API的滥用.首先,它违背Optional了顶部所述的主要设计目标.其次,它没有增加任何价值.

有三种方法可以处理缺少值Optional:提供替换值,调用函数以提供替换值,或抛出异常.如果要存储到字段中,则需要在初始化或分配时执行此操作.如果您正在将值添加到列表中,正如OP所提到的那样,您还可以选择不添加值,从而"消除"缺少的值.

我敢肯定有人可能会想出一些他们真正想要存放Optional在一个领域或一个集合中的人工案例,但总的来说,最好避免这样做.

  • 我不同意#3.当一个或多个值存在或不存在时,将"可选"作为一个字段是有用的. (41认同)
  • @StuartMarks,当我从所有的`Optional <>`专家那里听到它时,这是令我感到困惑的事情之一:"不要在一个字段中存储`Optional <>`.我真的想了解这个建议的重点.考虑一个DOM树,其中一个节点可以有父节点.在用例中似乎`Node.getParent()`将返回`Optional <Node>`.我真的希望存储一个"null"并每次包装结果吗?为什么?除了让我的代码看起来丑陋之外,这种额外的低效率会给我带来什么呢? (23认同)
  • 我不明白为什么在内部执行`if(foo == null)`会比仅将字段存储为`optional`更好.而且我不明白为什么在内部显式调用`getOptionalFoo()`比仅将字段存储为`Optional`更好.另外,如果一个字段可以是"null"而一个字段不能是"null",为什么不通过使它们成为"Optional"或"Optional"来将它传达给编译器. (12认同)
  • @GarretWilson另一方面,假设你有`Optional <Node> getParent(){return Optional.ofNullable(parent); }`.这*看起来很昂贵,因为它每次都会分配一个`Optional`!但是如果调用者立即解包它,那么该对象的寿命非常短暂,并且永远不会被提升到伊甸园空间之外.它甚至可能被JIT的逃逸分析所消除.底线答案是"它依赖",但在字段中使用`Optional`可能会增加内存使用并减慢数据结构遍历的速度.最后我认为它使代码混乱,但这是一个品味问题. (7认同)
  • @GarretWilson你是不是写了一篇博客?我会对此感兴趣:) (6认同)
  • “并且需要额外的指针引用”——如果我关心“额外的指针取消引用”(或十二个),我就不会使用 Java。 (5认同)
  • @mogronalol如果你想使用`Optional`作为字段,你可以;我们只是不推荐它。如果你这样做,我想你会发现你的代码变得相当混乱并且难以阅读。此外,“Optional”是另一个对象,因此每个“Optional”字段都会消耗更多空间并花费额外的指针引用。但如果您对这些问题没有异议,请继续。 (3认同)
  • @GarretWilson 如果您确定对字段使用`Optional` 对您的用例更有效,并且这样做会使您的代码看起来不错,那么好吧,继续使用它。我看不到你的代码。但我不得不说我有点怀疑。使用您的示例,假设您有一个带有 *N* 个节点的 DOM 树。如果你有 `Optional&lt;Node&gt; parent` 而不是一个可以为 null 的 `Node parent` 字段,那么你的 DOM 树中的对象数量就翻了一番。它还可能显着降低引用的局部性,从而在遍历过程中导致更多的缓存未命中。 (2认同)
  • @StuartMarks"如果调用者立即解压缩它"---是的,这似乎是用Java 8做出的另一个假设,因为只有在Java 9中才能得到诸如`Optional.or()`这样的东西,它最终实现了很多时候,获得可选项的东西将转向并将其传递给其他东西.当我们解开堆栈时,在"解包"和"Optional.ofNullable()ing"之间来回弹跳似乎很愚蠢(而且浪费) - 更不用说带回意外引用"null"的危险了,是什么`可选'应该首先防止. (2认同)

Nic*_*lai 69

我迟到了,但是为了它的价值,我想加上我的2分.他们违背了设计目标Optional,斯图尔特马克斯的回答很好地总结了这一点,但我仍然相信它们的有效性(显然).

使用Optional Everywhere

一般来说

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

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

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

请注意,这几乎不会允许Optional集合中的s几乎与nulls 一样糟糕.只是不要这样做.;)

关于你的问题

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

好处

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

澄清意图

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

消除不确定性

没有发生Optional的意义null尚不清楚.它可能是状态的合法表示(请参阅参考资料Map.get)或实现错误,例如缺少或失败的初始化.

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

更多空检查

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

缺点

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

性能

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

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

序列化

Optional不可序列化,解决方法并不过分复杂.

不变性

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

  • 我认为用例#2仍然非常值得怀疑.传递给方法的`Optional`可以是`null`.那么你获得了什么?现在你要进行两项检查.请参见http://stackoverflow.com/a/31923042/650176 (3认同)
  • @DavidV看看我给出的优势列表 - 它们都适用于这种情况.此外,如果你全押"可选"(如果你愿意将它作为参数传递,情况应该如此),`null`永远不是合法的价值.因此,使用显式参数"null"调用方法显然是一个错误. (2认同)

Pet*_*rey 26

就个人而言,我更喜欢使用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 = ..
Run Code Online (Sandbox Code Playgroud)

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

  • 嗯,那些IDEA注释?我个人更喜欢JSR 305的`@ Nonnull` - 与FindBugs合作;) (5认同)
  • 是的,这是真的; IDEA(13.x)给出了三种不同的选择......嗯,我总是最终使用JSR 305 (3认同)
  • 我知道这已经过时了,但关于注释和工具的讨论需要链接到http://stackoverflow.com/questions/35892063/which-nonnull-java-annotation-to-use#answer-35896657 - 哪个btw专门解决了Java 8的案例. (2认同)

Beh*_*ehe 24

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

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

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

Optional增加了一些开销,但我认为它的明显优势是明确 表示某个对象可能不存在并强制程序员处理这种情况.它可以防止有人忘记心爱的!= null支票.

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

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}
Run Code Online (Sandbox Code Playgroud)

if(soundcard != null){
  System.out.println(soundcard);
}
Run Code Online (Sandbox Code Playgroud)

对我来说,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的回答一样 - 也参见这个讨论.
  3. 你的书中的例子 - 我不确定我是否会在内部使用Optional,这可能取决于复杂性.对于一本书的"API",我会用一个Optional<Index> getIndex()明确表示该书可能没有索引.
  4. 我不会在集合中使用它,而是不允许在集合中使用空值

一般来说,我会尽量减少绕过nulls.(一旦被烧掉......)我认为找到合适的抽象是值得的,并向其他程序员表明某个返回值实际代表什么.

  • 你会写`soundcard.ifPresent(System.out :: println)`.调用`isPresent`在语义上与检查`null`相同. (14认同)
  • 对我来说,问题是具有可选类型的东西仍然可以为空。因此,为了真正完全安全,您必须执行类似“if(soundcard != null &amp;&amp; soundcard.isPresent())”的操作。虽然制作一个返回可选值并且也可以返回 null 的 api 是我希望没有人这样做的事情。 (3认同)
  • 我一直不明白的是:我们应该忘记 !=null,但我们永远不会忘记 .isPresent? (2认同)

cto*_*mek 13

来自Oracle教程:

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

  • API仅适用于方法的返回部分,对吧?由于类型擦除,选项不应该用作参数?有时候我在一个方法内部使用一个Optional,从一个可以为空的参数转换,或者从一个null构造函数参数初始化一个字段......仍然试图弄清楚这是否比将它留空为更有用 - 任何人都有任何想法? (3认同)

use*_*577 8

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

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

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

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

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


art*_*fex 6

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();
        }
    }
}
...
Run Code Online (Sandbox Code Playgroud)

变成了这个

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");
Run Code Online (Sandbox Code Playgroud)

通过使用Optional作为各个getter方法的返回值。

  • 在我看来,第一个示例比转换后的示例更具可读性。此外,第一个示例只能有一个 if 语句,而不是 3 个。 (6认同)