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

吸气方法是否返回Optional<Foo>类型代替经典的Foo是一种好习惯?假设该值可以是null

评论

尽管这可能会吸引有目的的答案,但这是一个好问题。我期待得到有关该主题的真实事实的答案。

问题是无效性是否不可避免。组件可能具有允许为null的属性,但是使用该组件的程序员仍然可以决定严格将该属性保持为非null。这样,程序员就不必再处理Optional了。或者换句话说,null确实表示缺少值,就像搜索结果一样(其中Optional是适当的),还是null只是一组可能值中的一个。

另请参阅有关@NotNull批注的讨论:stackoverflow.com/q/4963300/873282

#1 楼

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

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

我认为,常规地将其用作getter的返回值肯定会过度使用。

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

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

评论


(关于最后一部分)…并且当我们确信该值永远不会为null时,我们可以使用orElseThrow(AssertionError :: new),hem或orElseThrow(NullPointerException :: new)…

–霍尔格
2014年10月13日在18:37

如果您打算引入通用的Maybe或Some类型,您会做些什么不同?有没有一种方法使Optional不适合某个人,还是只是在新API上引入Optionals会使它成为非Java风格的?

– David Moles
15年2月13日在17:15

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

–布莱恩·格茨(Brian Goetz)
15年2月13日在21:26

我想知道为什么Java世界中的重点是Optional,而不是更好的静态分析。 Optional确实有一些优势,但是null具有的巨大优势是向后兼容性; Map :: get返回一个可为空的V,而不是Optional ,并且它永远不会改变。但是,它可以很容易地被注释为@Nullable。现在,我们有两种表达价值不足的方法,加上真正进行静态分析的动机更少,这似乎是一个更糟糕的情况。

– yshavit
2015年4月18日在16:22



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

–詹森·汤普森(Jason Thompson)
15年5月25日在22:06

#2 楼

在进行了一些我自己的研究之后,我遇到了许多可能建议何时合适的事情。最权威的是Oracle文章中的以下引文:
请注意,Optional类的目的不是替换每个单个null引用,而是其目的,这一点很重要。是为了帮助设计更容易理解的API,以便仅通过读取方法的签名就可以知道是否可以期望一个可选值。这迫使您主动展开一个Optional以应对缺少值的情况。” -厌倦了空指针异常?考虑使用Java SE 8的可选功能!


我也从Java 8可选内容中摘录了此内容:如何使用它


“可选功能并不意味着可以在这些情况下使用,因为它不会给我们带来任何好处:


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



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

我当时不是无法找到任何否定含义或危险信号,建议应避免使用Optional。我认为一般的想法是,如果有帮助或改善您的API的可用性,请使用它。

评论


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

–Vlasec
15年4月15日在12:52



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

–吉利
16年8月17日在18:25



我发现,需要将可选结果转换为其他API时会产生一些可选结果。结果是一个相当容易理解的API。见stackoverflow.com/a/31923105/105870

–异教徒卡尔
16-10-12在18:11

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

–softarn
17-10-2在11:57



麻烦的是,尽管开发人员有最好的意图,但它通常并没有改善API。我已经收集了Optional用法错误的示例,所有示例均取自生产代码,您可以检出这些代码。

–MiguelMunoz
18/12/15在3:49



#3 楼

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

评论


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

–布莱恩·格茨(Brian Goetz)
2014年10月12日18:44

#4 楼

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类可能是唯一使用它们的地方。)如果您打算在一些功能代码中使用吸气剂,则可能是最好有一个标准的getter,然后再有一个第二个getter,它返回Optional。

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

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

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

这会更改所有内容。

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

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

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

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

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

说了这么多,我仍然看到Nullness Checker使用的方法是最好的方法处理空值,因为它们将运行时错误中的NullPointerExceptions转换为编译时错误。

评论


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

–Archit
18年7月15日在6:03

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

–ParagFlume
19年1月15日在4:37

#5 楼

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


如果序列化层(通常是DB)允许表nullBAR列中某个单元格的FOO值,则吸气剂Foo.getBar()可以返回Optional,以指示开发人员可以合理地认为此值为null,他们应该对此进行处理。如果DB保证该值将不为null,则getter不应将其包装在Optional中。

Foo.bar应该是private而不是Optional。如果确实是Optional,则没有任何理由使用private
设置器Foo.setBar(String bar)应该采用bar而不是Optional的类型。如果可以使用null参数,请在JavaDoc注释中说明。如果不能正常使用nullIllegalArgumentException或某些合适的业务逻辑,恕我直言,则更合适。
构造函数不需要Optional参数(出于类似于第3点的原因)。通常,我只在构造函数中包括在序列化数据库中必须为非空的参数。

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