Java 8 getter应该返回可选类型吗?

时间:2014-10-12 17:30:18

标签: java java-8 nullable optional

Java 8中引入的

Optional类型对于许多开发人员来说是一个新事物。

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

5 个答案:

答案 0 :(得分:418)

当然,人们会做他们想做的事。但是在添加此功能时我们确实有一个明确的意图,并且是一个通用的类型,就像许多人希望我们这样做一样。我们的目的是为库方法返回类型提供一种有限的机制,其中需要一种明确的方式来表示&#34;没有结果&#34;,并且使用null这样的绝对可能导致错误。

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

我认为经常使用它作为吸气剂的返回值肯定会过度使用。

过度使用。

(公共服务公告:从不致电Optional.get,除非您能证明它永远不会为空;而是使用其中一种安全方法,例如orElse或{{1}回想起来,我们应该调用类似ifPresent之类的get或更清楚的东西,这是一个非常危险的方法,它首先破坏了getOrElseThrowNoSuchElementException的整个目的。经验教训。(更新:Java 10有Optional,在语义上等同于Optional.orElseThrow(),但其名称更合适。)

答案 1 :(得分:67)

在对自己进行了一些研究之后,我发现了一些可能暗示适当的事情。最具权威性的是来自Oracle文章的以下引用:

  

&#34;重要的是要注意Optional类的意图是不要替换每个空引用。相反,它的目的是帮助设计更易于理解的API ,这样只需读取方法的签名,就可以判断是否可以期望一个可选值。这会强制您主动打开Optional来处理缺少值。&#34; - Tired of Null Pointer Exceptions? Consider Using Java SE 8's Optional!

我还从Java 8 Optional: How to use it

中找到了这段摘录
  

&#34;可选不适合在这些环境中使用,因为它不会给我们任何东西:

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

这也似乎提出了一些有效的观点。

我无法找到任何负面含义或危险信号,表明应该避免使用Optional。我认为一般的想法是,如果它有用或者提高了API的可用性,请使用它。

答案 2 :(得分:16)

我总体上说,将可选类型用于可以为空的返回值是一个好主意。但是,w.r.t。到框架我假设用可选类型替换经典getter在使用依赖于getter和setter的编码约定的框架(例如,Hibernate)时会带来很多麻烦。

答案 3 :(得分:9)

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中的。 (该示例由blog by Brian Goetz提供。一个更好的示例可能使用orElse()方法,因为此代码无论如何都会引发异常,但您可以理解。)

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

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

如果您确实打算在一些功能代码中使用吸气剂,那么最好有一个标准的吸气剂,然后再准备一个返回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从运行时错误转换为编译时错误。

答案 4 :(得分:3)

如果您正在使用现代序列化程序和其他理解Optional的框架,那么我发现这些指南在编写Entity bean时运行良好域图层:

  1. 如果序列化层(通常是数据库)允许表null中列BAR中的单元格的FOO值,则getter Foo.getBar()可以返回{{ 1}}向开发人员指出该值可能合理地预期为null并且他们应该处理这个。如果数据库保证该值不为null,那么getter应该将其包装在Optional中。
  2. Optional应为Foo.barprivate。如果是Optional,那么Optional真的没有理由。
  3. 设置者private应采用Foo.setBar(String bar) bar的类型。如果可以使用Optional参数,则在JavaDoc注释中说明。如果不能使用null null或某些适当的业务逻辑,恕我直言,更合适。
  4. 构造函数不需要IllegalArgumentException个参数(原因类似于第3点)。通常我只在构造函数中包含必须在序列化数据库中为非null的参数。
  5. 为了提高上述效率,您可能需要编辑IDE模板以生成OptionaltoString()等的getter和相应模板,或者直接使用字段(大多数IDE生成器已经处理) with nulls)。