用于可选

现在使用Java 8已经有6个多月的时间了,对于新的API更改,我感到非常满意。 我还没有信心的一个领域是什么时候使用Optional 。 我似乎想要在任何地方使用它之间摇摆,可能是null ,根本就没有。

当我可以使用它的时候,似乎有很多情况,我也不确定是否增加了好处(IE可读性/ null安全性),或者只是造成额外的开销。

所以,我举几个例子,我对社区对是否增加好处的想法感兴趣。

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

 public Optional<Foo> findFoo(String id); 

2 – 作为param可能为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在集合?

任何我错过的情况?

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"); 

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

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

有三种方法可以处理Optional的缺失值:提供替代值,调用函数来提供替代值,或抛出异常。 如果你存储在一个字段中,你可以在初始化或分配时进行。 如果您将值添加到列表中,如OP所提到的那样,您可以选择不添加值,从而“扁平化”不存在的值。

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

我迟到了,但为了什么是值得的,我想补充2分。 他们违背了Optional的设计目标, Stuart Marks的回答很好地总结了这个目标 ,但我仍然相信它们的有效性(显然)。

无处不在使用

一般来说

我写了一篇关于使用Optional的完整博客文章,但基本上是这样的:

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

前两个异常可以减少Optional的包装和展开引用的感知开销。 他们的选择是这样的:一个null从来不能合法地从一个实例转移到另一个实例的边界。

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

关于你的问题

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

优点

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

澄清意图

Using Optional清楚地表示该变量是可选的。 你的代码或消费者的任何读者将被打败,事实上,可能什么都没有,并且在访问值之前需要检查。

消除不确定性

没有Optionalnull的意思是不清楚的。 它可能是一个状态的合法表示(请参阅Map.get )或像初始化失败或失败的实现错误。

随着Optional的持续使用,这个变化很大。 在这里, null的出现意味着存在一个错误。 (因为如果值被允许丢失,可以使用Optional 。)这使得调试空指针异常变得容易得多,因为这个null的含义问题已经得到了解答。

更多的空检查

现在什么都不可能是null ,这可以在任何地方执行。 无论是使用注释,断言还是普通的检查,您都不必考虑这个参数或返回类型是否为null。 它不能!

缺点

当然,没有银弹

性能

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

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

序列化

Optional不可序列化,但解决方法不是太复杂。

不变性

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

就我个人而言,我更喜欢使用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,不需要打包和展开值。 (或创建包装对象)

我认为番石榴可选和他们的维基页面相当好:

除了赋予null一个名字所带来的可读性的增加外,Optional的最大优点是它的防白痴。 如果你想要你的程序编译的话,它会迫使你主动思考这个缺失的情况,因为你必须主动打开Optional并处理这个问题。 Null使得简单地忘记事情变得非常容易,虽然FindBugs有帮助,但是我们不认为它解决了这个问题。

当您返回可能或不可能“存在”的值时,这一点尤其重要。 您(和其他人)更有可能忘记other.method(a,b)可能会返回空值,而您可能会忘记在执行other.method时可能为null。 返回Optional使得调用者不可能忘记这种情况,因为他们必须自己拆开对象来编译代码。 – (来源: 番石榴维基 – 使用和避免空 – 有什么意义? )

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); – 我不确定。 也许我会返回一个可能为或包含FooResult<Foo> 。 这是一个类似的概念,但不是一个真正的Optional
  2. public Foo doSomething(String id, Optional<Bar> barOptional); – 我更喜欢@Nullable和findbugs检查,就像在Peter Lawrey的答案中一样 – 另见这个讨论 。
  3. 您的书籍示例 – 我不确定是否在内部使用可选,这可能取决于复杂性。 对于本书的“API”,我将使用一个Optional<Index> getIndex()来显式指示该书可能没有索引。
  4. 我不会在集合中使用它,而不是在集合中允许空值

一般来说,我会尽量减少传递null 。 (一旦被烧毁)我认为找到适当的抽象并向同行的程序员指出一个确定的返回值实际上代表什么是值得的。

从Oracle教程 :

可选的目的不是要替换代码库中的每一个空引用,而是帮助设计更好的API,只要读取一个方法的签名,用户就可以知道是否期望一个可选的值。 另外,可选强制你主动打开一个Optional来处理没有值的情况; 因此,您可以保护您的代码免遭意外的空指针异常。

已经有几个关于这个功能的资源:

  • 厌倦了空指针异常? 考虑使用Java SE 8的可选
  • 选项类型 :“表示封装可选值的多态类型”
  • Java 8可选:如何使用它
  • 可选项目的设计
  • Monadic Java
  • 使用Java SE 8 Streams处理数据

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

我打算严格测试我的一个项目,因此我建立了主张; 只有有些事情我必须去核实,有些则不是。

因此,我建立了一些东西来断言并使用断言来验证它们,如下所示:

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

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

看来只有当Optional中的T类型是像intlongchar等原始类型时,Optional才是有用的。对于“真实”类,它对我来说是没有意义的,因为无论如何你都可以使用null值。

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

Nullable<T>

在C#中,这个Nullable<T>很早就被引入来包装值类型。

我不认为可选是可能返回空值的方法的一般替代。

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

主叫方的选择权的主要信息是,他可能不指望给定的值,但可能在某个时间可用。 也许它会再次消失,并再次回来一次。 它就像一个开/关开关。 你有“选择”打开或关闭灯。 但是如果你没有开灯的话,你没有选择。

所以我觉得太乱了,以至于可能返回以前为null的地方引入了Optionals。 我仍然会使用null,但是只能在树的根,惰性初始化和明确的find-methods等限制区域使用。

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

您可以创建一个空的可选或可选的空值。

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

这里是一个可选的非空值:

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

如果价值存在,就做些什么

既然你有一个可选对象,你可以访问可用的方法来明确地处理值的存在与否。 而不必记得做一个空的检查,如下所示:

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