Java 8 getters是否应该返回可选类型?


288

Optional Java 8中引入的type对许多开发人员来说都是新事物。

用getter方法返回Optional<Foo>类型代替经典方法Foo是一种好习惯吗?假设值可以是null


8
尽管这很可能会引起有根据的答案,但这是一个很好的问题。我期待有关该问题的真实事实的答案。
贾斯汀

8
问题是无效性是否不可避免。组件可能具有允许为null的属性,但是使用该组件的程序员仍可能决定严格将该属性保持为non- null。因此,程序员Optional那时不必处理。或者,换句话说,null确实表示缺少值,例如搜索结果(Optional适当时),或者null只是可能值集合中的一个。
Holger 2014年

1
另请参见有关@NotNull注释的讨论:stackoverflow.com/q/4963300/873282
koppor

Answers:


515

当然,人们会做他们想要的。但是添加此功能时我们确实有明确的意图,并且它并不是通用的Maybe类型,因为许多人希望我们这样做。我们的意图是为库方法的返回类型提供一种有限的机制,该机制需要一种明确的方式来表示“无结果”,并且使用null这种方法极有可能导致错误。

例如,您可能永远不要将其用于返回结果数组或结果列表的东西;而是返回一个空数组或列表。您几乎永远不应将其用作某些内容或方法参数的字段。

我认为,通常将其用作获取方法的返回值肯定会过度使用。

应该避免使用Optional并没有什么,这并不是很多人希望的那样,因此我们非常担心过度使用的风险。

(公共服务公告:除非您可以证明它永远不会为null,否则请不要调用Optional.get;而应使用诸如orElse或的安全方法之一ifPresent。回想起来,我们应该调用get诸如getOrElseThrowNoSuchElementException或之类的东西,使之更加清楚地表明这是一种非常危险的方法这首先破坏了的整个目的Optional。经验教训(更新:Java 10具有Optional.orElseThrow(),在语义上与等效get(),但是其名称更合适。)


24
(关于最后一部分)…而且当我们有信心时,永远也不会null使用这个值,我们可能会使用orElseThrow(AssertionError::new),哼或orElseThrow(NullPointerException::new)……
Holger 2014年

25
你会怎么做不同,如果你的意图已经引进了通用也许或某些类型的?有没有一种方法使Optional不适合某个人,还是只是在新API上引入Optionals会使它成为非Java风格的?
大卫·摩尔

22
我所说的“通用类型”是指将其构建到语言的类型系统中,而不是提供一个近似它的库类。(某些语言具有T?(T或null)和T!(不可空T)的类型)。我们不能像语言支持那样在Foo和Optional <Foo>之间进行隐式转换。
Brian Goetz

11
我想知道为什么Java世界中的重点是Optional,而不是更好的静态分析。Optional确实有一些优势,但是具有的巨大优势null是向后兼容。Map::get返回一个nullable V,而不是一个Optional<V>,并且永远不会改变。但是,它可能很容易被注释@Nullable。现在,我们有2点的方式来表达缺的价值,再加上没有动力真正得到静态分析发生的事情,这似乎是一个更糟糕的处境是英寸
yshavit

21
我不知道使用它作为属性的返回值并不是所有人都想要的,直到我在StackOverflow上阅读此答案。实际上,我被认为是您在Oracle网站上阅读本文后的全部意图: oracle.com/technetwork/articles/java/… 谢谢您的澄清
Jason Thompson

73

在进行了一些我自己的研究之后,我遇到了一些可能建议何时合适的事情。最权威的是Oracle文章中的以下引文:

“重要的是要注意,Optional类的目的不是替换每个单个null引用。相反,它的目的是帮助设计更易于理解的API,以便仅通过读取方法的签名即可知道是否可以期望一个可选值。这将迫使您主动展开一个可选值以应对缺少值的情况。” - 厌倦了空指针异常?考虑使用Java SE 8的可选功能!

我还从Java 8 Optional:How to use it中找到了此摘录

“可选在这些情况下并不适用,因为它不会给我们带来任何好处:

  • 在域模型层(不可序列化)
  • 在DTO中(相同原因)
  • 在方法的输入参数中
  • 在构造函数参数中”

这似乎也提出了一些合理的观点。

我找不到任何负面含义或危险信号,建议Optional应避免这种情况。我认为一般的想法是,如果有帮助或改善API的可用性,请使用它。


10
stackoverflow.com/questions/25693309-似乎杰克逊已经支持了,因此“不可序列化”不再作为有效的理由:)
Vlasec

1
我建议您有一个答案地址,为什么Optional在方法(更具体地说是构造函数)的输入参数中使用“不会买任何东西”。dolszewski.com/java/java-8-optional-use-cases包含一个很好的解释。
吉利

我发现,需要将可选结果转换为其他API会产生一些可选结果。结果是一个相当容易理解的API。参见stackoverflow.com/a/31923105/105870
异教徒卡尔(Karl the Pagan)

1
链接断开。您能否更新答案?
softarn

2
麻烦的是,尽管开发人员有最好的意图,但它通常并没有改善API。我已经收集了Optional用法不好的示例,所有示例都来自生产代码,您可以查看这些代码。
MiguelMunoz '18

20

我通常会说将可选类型用于可以为空的返回值是一个好主意。但是,对于框架,我认为用可选类型替换经典的getter会在使用依赖于getter和setter编码约定的框架(例如,Hibernate)时引起很多麻烦。


14
这个建议恰好是我在stackoverflow.com/a/26328555/3553087中 “我们担心热心过度使用的风险”的意思。
Brian Goetz 2014年

13

Optional添加到Java 的原因是因为:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

比这更干净:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

我的观点是,Optional是为了支持函数式编程编写的,该函数是同时添加到Java中的。(该示例由Brian Goetz博客提供。一个更好的示例可能使用该orElse()方法,因为无论如何此代码都会引发异常,但您可以理解。)

但是现在,人们使用Optional的原因非常不同。他们正在使用它来解决语言设计中的缺陷。缺陷在于:无法指定API的哪个参数和返回值允许为空。它可能会在javadocs中提到,但是大多数开发人员甚至都没有为他们的代码编写javadocs,并且没有多少人会在编写时检查javadocs。因此,这导致很多代码在使用空值之前总是检查空值,即使它们经常不可能为空值也是如此,因为它们已经在调用堆栈中重复验证了九到十次。

我认为真正需要解决此缺陷,因为看到新Optional类的许多人都认为它的目的是增加API的清晰度。这就是为什么人们会问诸如“吸气剂应返回可选件”之类的问题吗?不,他们可能不应该这样做,除非您希望将getter用于函数式编程中,否则这种可能性很小。实际上,如果您查看Optional在Java API中的使用位置,那么它主要在Stream类中,Stream类是函数式编程的核心。(我还没有进行非常彻底的检查,但是Stream类可能是唯一使用它们的地方。)

如果确实打算在一些功能代码中使用吸气剂,那么最好使用标准的吸气剂,而第二个吸气剂则返回Optional。

哦,如果您需要类可序列化,则绝对不要使用Optional。

可选参数是解决API缺陷的非常糟糕的解决方案,因为a)它们非常冗长,并且b)从来没有打算首先解决该问题。

Nullness Checker是API缺陷的更好解决方案。这是一个注释处理器,通过使用@Nullable对其进行注释,您可以指定允许哪些参数和返回值为空。这样,编译器可以扫描代码并确定是否将实际上可以为null的值传递给不允许为null的值。默认情况下,它假定没有任何内容可以为null,除非有注释。这样,您不必担心空值。将空值传递给参数将导致编译器错误。测试对象的null不能为null会产生编译器警告。这样做的结果是将NullPointerException从运行时错误更改为编译时错误。

这改变了一切。

至于您的吸气剂,请不要使用Optional。并尝试设计您的类,以便所有成员都不能为空。也许尝试将Nullness Checker添加到项目中,并在需要时声明@Nullable的getter和setter参数。我只在新项目中做到了这一点。它可能会在现有项目中产生很多警告,这些警告中包含许多多余的null测试,因此可能很难进行改进。但这也会捕获很多错误。我喜欢它。因此,我的代码更加简洁,可靠。

(还有一种新的语言可以解决这个问题。Kotlin可以编译为Java字节码,它允许您指定在声明对象时是否可以为null。这是一种更干净的方法。)

原始帖子的附录(版本2)

经过深思熟虑,我很不情愿地得出一个结论,在一个条件下返回Optional是可以接受的:检索到的值实际上可能为null。我看过很多代码,人们通常从无法正常返回null的getter中返回Optional。我认为这是一种非常糟糕的编码实践,只会增加代码的复杂性,从而更容易出现错误。但是,当返回的值实际上可能为null时,请继续将其包装在Optional中。

请记住,为函数式编程设计的方法和需要函数引用的方法将(并且应该)以两种形式编写,其中一种使用Optional。例如,Optional.map()Optional.flatMap()都采用函数引用。第一个引用一个普通的getter,第二个引用一个返回Optional的引用。因此,如果返回值不能为null的Optional,则不会帮任何忙。

综上所述,我仍然看到Nullness Checker所使用的方法是处理null的最佳方法,因为它们将NullPointerExceptions从运行时错误转换为编译时错误。


2
这个答案对我来说似乎最合适。可选仅在添加流时才在Java 8中添加。据我所知,只有流函数返回可选的。
Archit

1
我认为这是最好的答案之一。人们知道什么是可选的以及它是如何工作的。但是最令人困惑的部分是/在哪里使用它以及如何使用它。通过阅读本文,可以消除很多疑问。
ParagFlume

3

如果您使用的是现代的序列化程序和其他了解的框架,Optional那么我发现这些准则在编写Entitybean和域层时效果很好:

  1. 如果序列化层(通常是DB)在table的nullBAR中允许一个单元格的值FOO,则getter Foo.getBar()可以返回以Optional指示开发人员该值可以合理地期望为null,并且他们应该对此进行处理。如果DB保证值不为空,则吸气应该不会在包装这个Optional
  2. Foo.bar应该是privateOptional。确实没有理由为它是Optional,如果它是private
  3. 二传手Foo.setBar(String bar)应该采取的类型bar Optional。如果可以使用null参数,请在JavaDoc注释中说明。如果不是OK使用null一个IllegalArgumentException或一些适当的商业逻辑是,恕我直言,更合适。
  4. 构造函数不需要Optional参数(出于类似于第3点的原因)。通常,我只在构造函数中包括在序列化数据库中必须为非null的参数。

为了使上述操作更加有效,您可能需要编辑IDE模板以生成的getter和相应的模板toString()equals(Obj o)等等,或者直接使用字段(大多数IDE生成器已经处理了null)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.