我应该在每个可能返回null的方法中使用Java8/Guava Optional吗?

62

Optional用于表示可空对象,该类的一些用途包括:

  1. 作为方法返回类型,作为返回null的替代方案,表示没有可用值。
  2. 区分“未知”(例如,在地图中不存在)和“已知没有值”的情况(在地图中存在,并带有值Optional.absent())。
  3. 将可空引用包装为集合中的存储对象,该集合不支持null(虽然有几种其他方法也应该首先考虑)。

对于第一种情况,我是否需要在所有可为空的返回方法中返回Optional?


1
https://code.google.com/p/guava-libraries/wiki/UsingAndAvoidingNullExplained - assylias
2
博客文章描述了在Java SE 8中如何使用可选项,以及不应该使用它的情况。 - JodaStephen
2
请参阅Java 8的getter方法是否应该返回Optional类型?,其中包括Brian Goetz的答案。 - Jeffrey Bosboom
3个回答

75

那么 Optional 有什么问题吗?

我们所面临的问题是:JDK 8 Optional 对象是否会消除空引用?答案是强烈的否定!因此,反对者立刻质疑其价值,问道:那么它有什么用处,我们不能通过其他方式达到呢?

与 SML 或 Haskell 等函数式语言从未存在过 null 引用的情况不同,在 Java 中,我们无法简单地摆脱历史遗留下来的 null 引用。这将继续存在,并且他们可谓有其适当的用途(只是举个例子:三值逻辑)。

我怀疑 Optional 类的意图并非替换每个可为空的引用,而是帮助创建更健壮的 API,使我们可以通过读取方法签名来判断是否可以期望一个可选值,并强制程序员相应地使用该值。但最终,Optional 将只是另一个引用,并受到语言中每个其他引用的同样弱点的影响(例如,您可以返回一个 null Optional)。很明显,Optional 不会拯救这一天。

这些可选对象在Java中的使用方式以及它们是否有价值一直是项目lambda邮件列表中激烈辩论的话题。从反对者那里我们听到了一些有趣的论点,比如:
  • 其他选择存在的事实(例如,像IntelliJ和Eclipse IDE这样的IDES支持一组专有的注释进行空值的静态分析JSR-305具有@Nullable和@NonNull等注释)。
  • 有些人希望它像函数式编程语言一样可用,但在Java中这并不完全可能,因为该语言缺少许多函数式编程语言(例如SML或Haskell)中存在的功能(例如模式匹配)。
  • 其他人认为,将现有代码改造为使用此模式是不可能的(例如,List.get(Object)将继续返回null)。
  • 还有一些人抱怨缺乏对可选值的语言支持会创建潜在的情况,在API中可能不一致地使用Optional,从而创建不兼容性,就像我们与无法改造以使用新的Optional类的其余Java API一样。这基本上就是你在这里提出的问题。在支持可选类型的语言如Ceylon如Kotlin中,您甚至不会质疑这一点。
  • 一个令人信服的论点是,如果程序员调用可选对象中的get方法,如果为空,它将引发NoSuchElementException,这几乎与nulls存在相同的问题,只是使用不同的异常。
因此,Optional的好处似乎真的值得怀疑,并且可能仅限于提高可读性和强制执行公共接口合同。我确信采用这种Optional函数语法可能会使我们的代码更安全,更不容易出现空指针引用问题,从而更加健壮和少出错。当然,这不是一个完美的解决方案,因为毕竟,Optional引用也可能被错误地设置为null引用,但我希望程序员遵循不在期望使用可选对象的位置传递null引用的约定,就像我们今天认为不在期望使用集合或数组的位置传递null引用是一种良好的实践,在这些情况下,正确的方法是传递一个空数组或集合。这里的重点是现在我们有了一个API中可以使用的机制,可以明确表示对于给定的引用,我们可能没有一个值来分配它,用户被API强制验证这一点。
引用Google Guava's article关于可选对象的使用:
“除了将 null 命名使代码更易读之外,Optional 的最大优点是它的傻瓜式特性。如果你想让程序编译通过,你必须积极思考缺失情况,因为你必须积极地解包 Optional 并处理这种情况。”
所以,我想每个 API 设计者都可以选择在 Optional 的使用上走多远。
一些有影响力的开发者,如 Stephen Colebourne 和 Brian Goetz,最近发表了一些有关正确使用 Optional 的有趣文章。 我尤其觉得以下文章很有用:
- Java Getter 是否应该返回 Optional - Java SE 8 Optional,务实的方法

1
当你说用户被API强制验证时,你能告诉我在Java 8中如何实现吗?因为用户可能会调用get(),这会导致抛出异常。 - Arghya Sadhu
3
我认为 Optional API 最大的错误是公开了一个可能抛出 NoSuchElementException 异常的 “get” 方法。应该始终通过 “orElse” 或 “orElseGet” 方法解包 Optional,以便始终可以返回一个值,最好是非空值。如果不想解包值,则始终可以使用 “isPresent” 方法进行检查。在我的工作中,我总是使用 map、flatMap、filter lambda 方法来处理 Optional。 - ailveen

8
与Guava相比,java.util.Optional 的一个令人恼火的问题是它没有提供像 orElse(Optional<T>): Optional<T> 这样的方法,但是在另一方面,com.google.common.base.Optional 中定义了这个方法:or(Optional<T>): Optional<T>。缺乏这个特定功能限制了Java 8的Optional的单子应用。
更新:可以将Guava的or(Optional<T>)复制为Java 8 Optional中的形式。

optionalA.map(Optional::of).orElse(optionalB)

或者

optionalA.map(Optional::of).orElseGet(() -> computeOptionalB)

更新:

Java 9中(终于!)你可以使用Optional.or(Supplier<Optional<T>>)

optionalA.or(() -> computeOptionalB)

很棒!


1
@JeffreyBosboom,opt.flatMap(fn) 相当于 opt.isPresent()?fn.apply(opt.value):opt,而 opt.orElse(opt2) 则相当于 opt.isPresent()?opt:opt2。你能发现它们之间的区别吗? - Vlad
java.util.Optional中有T orElse(T other)方法。https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#orElse-T- - Adam Siemion
@AdamSiemion,orElse(T)生成T,而orElse(Optional <T>)生成Optional <T>。你能看出两者之间的区别吗? - Vlad
5
Java团队真是太棒了,他们意识到Guava的Optional很有用,将其添加到Java 8中,并确保这两者在细微之处存在不兼容性! - user711413

8
作为观察,我认为在设计应用程序时必须考虑的最重要方面之一是决定如何处理“空问题”。 在这方面,第一步将是确定可能的“null源”。例如,项目中使用的数据库或外部库。
下一步将是“包含”问题,即包装有问题的代码(使用Optional)并阻止空值在整个系统中传播,因为不知情的代码可能会触发NPE。
回答您的问题,这取决于......大多数情况下我会说是的,特别是针对存在于应用程序不同“关注点”(或层)的薄边界的方法(例如,您用于查询数据存储的接口/类的签 名,或用于“传输”可能具有空属性的数据的pojos - DTO或更一般的描述-不同模块的发布API)应该避免将空值泄漏到其他具有不同关注点的区域。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接