首页 文章

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

提问于
浏览
217

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

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

5 回答

  • 13

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

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

    我认为通常使用它作为吸气剂的返回值绝对会过度使用 .

    可选的应该避免的是没有错,这不是很多人所希望的,因此我们非常担心过度使用的风险 .

    (公共服务公告:永远不要调用 Optional.get ,除非你能证明它永远不会为空;而是使用安全方法之一,如 orElseifPresent . 回想起来,我们应该调用 get 之类的东西,比如 getOrElseThrowNoSuchElementException ,或者是什么使得它更清楚从一开始就是破坏 Optional 的全部目的的一种非常危险的方法 . 经验教训 . (更新:Java 10有 Optional.orElseThrow() ,它在语义上等同于 get() ,但其名称更合适 . ))

  • 7

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

    “重要的是要注意Optional类的意图不是替换每个单独的null引用 . 相反,它的目的是帮助设计更易于理解的API,这样只需读取方法的签名,就可以判断你可以期待一个可选的值 . 这会迫使你主动打开一个Optional来处理缺少值 . “ - 厌倦了空指针异常?考虑使用Java SE 8的可选项!

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

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

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

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

  • 375

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

  • 2

    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中提到过,但是大多数开发人员甚至都没有为他们的代码编写javadoc,并且没有多少人会在编写时检查javadoc . 因此,这会导致许多代码在使用它们之前始终检查空值,即使它们通常不能为空,因为它们已经在调用堆栈上重复验证了九到十次 .

    我认为解决这个漏洞真的很渴望,因为很多人看到新的Optional类的目的是为了增加API的清晰度 . 这就是为什么人们会问像_588302这样的问题 . 不,他们可能不应该主要在Stream类中,这是函数式编程的核心 . (我没有仔细检查过,但Stream类可能是他们唯一使用的地方 . )

    如果你计划在一些功能代码中使用getter,那么最好有一个标准的getter和一个返回Optional的第二个getter .

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

    可选项是API缺陷的一个非常糟糕的解决方案,因为a)它们非常冗长,并且b)它们从未打算解决那个问题首先出现了 .

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

    这改变了一切 .

    至于你的getter,不要使用Optional . 并尝试设计您的类,以便没有成员可能为null . 并且可以尝试将Nullness Checker添加到您的项目中,并在需要时声明您的getter和setter参数@Nullable . 我只用新项目做了这件事 . 它可能会在现有项目中产生很多警告,这些项目使用了大量多余的null测试,因此改造可能很难 . 但它也会遇到很多错误 . 我喜欢它 . 因为它,我的代码更清晰,更可靠 .

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

    Addendum to Original Post (version 2)

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

    请记住,为函数式编程而设计的方法以及需要函数引用的方法将(并且应该)以两种形式编写,其中一种使用Optional . 例如, Optional.map()Optional.flatMap() 都采用函数引用 . 第一个引用普通的getter,第二个引用一个返回Optional的引用 . 所以你're not doing anyone a favor by return an Optional where the value can'是空的 .

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

  • 62

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

    • 如果序列化层(通常是DB)允许表 FOO 中列 BAR 中的单元格的 null 值,则getter Foo.getBar() 可以返回 Optional ,向开发人员指示该值可能合理地预期为null并且他们应该处理此 . 如果DB保证该值不为null,则getter应该 not 将其包装在 Optional 中 .

    • Foo.bar 应为 privatenotOptional . 如果它是 private ,那么它真的没有理由 Optional .

    • setter Foo.setBar(String bar) 应采用 barnot Optional 的类型 . 如果可以使用 null 参数,则在JavaDoc注释中说明 . 如果不能使用 nullIllegalArgumentException 或某些适当的业务逻辑,恕我直言,更合适 .

    • 构造函数不需要 Optional 参数(原因类似于第3点) . 通常,我只在构造函数中包含 must 在序列化数据库中为非null的参数 .

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

相关问题