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

Posted

技术标签:

【中文标题】Java 8 getter 是不是应该返回可选类型?【英文标题】:Should Java 8 getters return optional type?Java 8 getter 是否应该返回可选类型? 【发布时间】:2014-12-07 07:23:09 【问题描述】:

Java 8 中引入的Optional 类型对于很多开发者来说是个新鲜事物。

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

【问题讨论】:

虽然这可能会引起固执己见的回答,但这是个好问题。我期待着关于这个主题的真实事实的答案。 问题是nullablity是否不可避免。一个组件可能有一个允许为空的属性,但是,使用该组件的程序员可能会决定严格保留该属性非null。所以程序员不应该在那时处理Optional。或者,换句话说,null 是否真的代表没有像搜索结果那样的值(Optional 是合适的)或者null 只是一组可能值中的一个成员。 参见@NotNull注解的讨论:***.com/q/4963300/873282 【参考方案1】:

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

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

我认为经常使用它作为 getter 的返回值肯定会被过度使用。

应该避免使用 Optional 并没有任何错误,这不是许多人希望的那样,因此我们相当担心过度使用的风险。

(公共服务公告:绝不调用Optional.get,除非您能证明它永远不会为空;而是使用orElseifPresent 等安全方法之一。回想起来,我们应该将get 称为getOrElseThrowNoSuchElementException 之类的名称,或者更清楚地表明这是一种高度危险的方法,首先破坏了Optional 的全部目的。吸取的教训。(更新:Java 10 有@987654329 @,在语义上等价于get(),但它的名字更合适。))

【讨论】:

(关于最后一部分)……当我们确信该值永远不是 null 时,我们可能会使用 orElseThrow(AssertionError::new)、ahem 或 orElseThrow(NullPointerException::new)…… 如果您的意图是 是引入通用的 Maybe 或 Some 类型,您会有什么不同的做法?有没有一种方法使 Optional 不适合一个人,或者仅仅是在一个新的 API 中引入 Optional 会使其非 Java 风格? “通用类型”是指将它构建到语言的类型系统中,而不是提供一个近似它的库类。 (有些语言有 T?(T 或 null)和 T!(不可为空的 T)的类型) Optional 只是一个类;我们不能像语言支持那样在 Foo 和 Optional 之间进行隐式转换。 我一直想知道为什么 Java 世界的重点是可选的,而不是更好的静态分析。 Optional 确实有一些优势,但null 的巨大优势是向后兼容; Map::get 返回一个可为空的V,而不是Optional<V>,这永远不会改变。不过,它很容易被注释为@Nullable。现在我们有两种方法来表达缺乏价值,加上真正进行静态分析的动机更少,这似乎是一个更糟糕的位置。 直到我在 *** 上阅读了这个答案,我才知道使用它作为属性的返回值并不是你们所有人想要的。事实上,在阅读 Oracle 网站上的这篇文章后,我被引导相信这正是你们所有人的意图:oracle.com/technetwork/articles/java/… 谢谢你的澄清【参考方案2】:

在进行了一些自己的研究之后,我发现了一些可能会在适当的时候提出建议的事情。最权威的是 Oracle 文章中的以下引用:

“值得注意的是,Optional 类的意图是不是替换每个空引用。相反,它的目的是帮助设计更易于理解的 API 这样通过仅读取方法的签名,您就可以判断您是否可以期望一个可选值。这会迫使您主动解包 Optional 以处理缺少值的情况。" - @ 987654321@

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

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

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

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

我找不到任何负面含义或危险信号来暗示应该避免使用Optional。我认为总体思路是,如果它有帮助或提高 API 的可用性,请使用它。

【讨论】:

***.com/questions/25693309 - Jackson 似乎已经支持它,所以“不可序列化”不再作为正当理由 :) 我建议您回答为什么在方法(更具体地说是构造函数)的输入参数中使用Optional“不会给我们买任何东西”。 dolszewski.com/java/java-8-optional-use-cases 包含一个很好的解释。 我发现柯里化需要转换为其他 API 的可选结果需要一个 Optional 参数。结果是一个相当易于理解的 API。见***.com/a/31923105/105870 链接已损坏。你能更新答案吗? 问题是,尽管开发人员的意图是最好的,但它通常不会改进 API。我一直在收集bad usages of Optional的例子,全部取自生产代码,大家可以看看。【参考方案3】:

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

【讨论】:

这条建议正是我在***.com/a/26328555/3553087 中所说的“我们担心过度使用的风险”。【参考方案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 中。 (这个例子来自blog by Brian Goetz。更好的例子可能是使用orElse() 方法,因为这段代码无论如何都会抛出异常,但你明白了。)

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

我认为解决这个缺陷真的很迫切,因为很多看到新 Optional 类的人都认为它的目的是增加 API 的清晰度。这就是为什么人们会问诸如“getter 应该返回 Optionals 吗?”之类的问题。不,他们可能不应该,除非您希望 getter 用于函数式编程,这不太可能。事实上,如果你看Java API中Optional的使用情况,主要是在Stream类中,它们是函数式编程的核心。 (我没有仔细检查过,但 Stream 类可能是唯一使用它们的地方。)

如果您确实打算在一些函数式代码中使用 getter,那么拥有一个标准 getter 和另一个返回 Optional 的 getter 可能是个好主意。

哦,如果你需要你的类是可序列化的,你绝对不应该使用 Optional。

选项对于 API 缺陷来说是一个非常糟糕的解决方案,因为 a)它们非常冗长,并且 b)它们从一开始就没有打算解决这个问题。

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

这改变了一切。

至于你的吸气剂,不要使用可选的。并尝试设计您的类,使所有成员都不可能为空。也许尝试将 Nullness Checker 添加到您的项目中,并在需要时声明您的 getter 和 setter 参数@Nullable。我只在新项目中这样做过。它可能会在现有项目中产生很多警告,其中包含大量多余的 null 测试,因此可能很难进行改造。但它也会捕获很多错误。我喜欢它。正因为如此,我的代码更干净、更可靠。

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

原帖附录(第 2 版)

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

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

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

【讨论】:

这个答案对我来说似乎最合适。 Optional 仅在添加流时在 java 8 中添加。据我所知,只有流函数返回可选。 我认为这是最好的答案之一。人们知道什么是可选的以及它是如何工作的。但最令人困惑的部分是/是在哪里使用它以及如何使用它。读到这里,你就可以解开很多疑惑了。【参考方案5】:

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

    如果序列化层(通常是数据库)允许表FOOBAR 列中的单元格的null 值,则getter Foo.getBar() 可以返回Optional,向开发人员表明该值可以合理地预期为空,他们应该处理这个。如果 DB 保证该值不会为 null,则 getter 应将其包装在 Optional 中。 Foo.bar 应该是 private应该是 Optional。如果是private,那真的没有理由是Optional。 setter Foo.setBar(String bar) 应该采用bar Optional 的类型。如果可以使用null 参数,则在JavaDoc 注释中说明这一点。如果不能使用nullIllegalArgumentException 或一些适当的业务逻辑,恕我直言,更合适。 构造函数不需要Optional 参数(原因类似于第3 点)。一般来说,我只在构造函数中包含必须在序列化数据库中为非空的参数。

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

【讨论】:

使用 Lombok,我认为使用具有默认空 Optional 值和生成的 setter 和 getter 的 Optional 私有字段是个好主意... 但是 Lombok 的开发者不同意 :) ***.com/a/31674917 除了4:不同的静态工厂方法e.g. Foo.withBar(...)Foo.withoutBar(...) 如果取决于 bar 的存在与否,也会有不同的验证逻辑。但是你也可以考虑让它成为两个子类或两个不同的类来实现相同的接口。

以上是关于Java 8 getter 是不是应该返回可选类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥不应该在参数中使用 Java 8 的 Optional

为啥不应该在参数中使用 Java 8 的 Optional

8. java 面向对象

可选与空。 Java 8 中 Optional 的目的是啥? [复制]

OC学习-8存取器方法?getter和setter?事实上就是赋值和返回值的两种函数

我应该如何在 Firebase 实时数据库中设置作为键值类型的 getter 和 setter?