ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

尽管这可能会吸引固执己见的答案,但这是一个很好的问题。我期待着关于这个主题的真实事实的答案。
问题是无效性是否不可避免。一个组件可能有一个允许为 null 的属性,但是,使用该组件的程序员可能会决定严格保持该属性为非 null。所以程序员不应该再处理 Optional 。或者,换句话说,null 是否真的表示缺少与搜索结果类似的值(其中 Optional 是合适的)或者 null 只是可能值集的一个成员。
另请参阅关于 @NotNull 注释的讨论:stackoverflow.com/q/4963300/873282

B
Brian Goetz

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

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

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

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

(公共服务公告:绝不调用 Optional.get,除非你能证明它永远不会为空;而是使用安全方法之一,如 orElseifPresent。回想起来,我们应该调用get 类似 getOrElseThrowNoSuchElementException 的东西,或者更清楚地表明这是一种高度危险的方法,它首先破坏了 Optional 的全部目的。吸取的教训。(更新:Java 10 有 Optional.orElseThrow(),它在语义上等价于 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。现在我们有两种的方式来表达缺乏价值,加上真正进行静态分析的动机更少,这似乎是一个更糟糕的位置。
在我在 StackOverflow 上阅读此答案之前,我不知道将其用作属性的返回值并不是你们所有人的意图。事实上,在阅读 Oracle 网站上的这篇文章后,我被引导相信这正是你们所有人的意图:oracle.com/technetwork/articles/java/… 感谢您的澄清
C
Corey

在做了一些我自己的研究之后,我遇到了一些可能暗示什么时候合适的事情。最权威的是 Oracle 文章中的以下引用:

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

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

“可选并不意味着在这些上下文中使用,因为它不会给我们带来任何好处:在 DTO 中的域模型层(不可序列化)中(同样的原因)在构造函数参数中的方法的输入参数中”

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

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


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

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


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

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 从运行时错误更改为编译时错误。

这改变了一切。

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

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

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

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

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

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


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

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

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

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


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

您必须记住,经常被引用的建议来自那些在 Java 之外几乎没有经验的人,他们对选项类型或函数式编程几乎没有经验。

所以带着一粒盐吃。相反,让我们从“良好实践”的角度来看它:

良好的实践不仅意味着问“我们如何编写新代码?”,还意味着“现有代码会发生什么?”。

Optional 的情况下,我的环境找到了一个很好且易于理解的答案:

Optional 是强制性的,以指示记录中的可选值:record Pet(String name, Optional Breed, Optional dateOfBirth)

这意味着现有代码按原样是好的,但使用 record 的代码(即“新代码”)会导致围绕它的 Optional 广泛采用。

结果在可读性和可靠性方面取得了圆满成功。停止使用 null