ChatGPT解决这个技术问题 Extra ChatGPT

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

我在许多网站上阅读过 Optional 应该只用作返回类型,而不是在方法参数中使用。我正在努力寻找一个合乎逻辑的原因。例如,我有一段逻辑,它有 2 个可选参数。因此,我认为像这样编写我的方法签名是有意义的(解决方案 1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

许多网页指定 Optional 不应用作方法参数。考虑到这一点,我可以使用以下方法签名并添加明确的 Javadoc 注释来指定参数可能为空,希望未来的维护人员会阅读 Javadoc,因此在使用参数之前始终执行空检查(解决方案 2) :

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

或者,我可以用四个公共方法替换我的方法,以提供更好的接口并使其更明显 p1 和 p2 是可选的(解决方案 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

现在我尝试编写类的代码,它为每种方法调用这段逻辑。我首先从另一个返回 Optional 的对象中检索两个输入参数,然后调用 calculateSomething。因此,如果使用解决方案 1,调用代码将如下所示:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

如果使用解决方案 2,调用代码将如下所示:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

如果应用了解决方案 3,我可以使用上面的代码,或者我可以使用以下代码(但它的代码要多得多):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

所以我的问题是:为什么使用 Optionals 作为方法参数被认为是不好的做法(请参阅解决方案 1)? 这对我来说似乎是最易读的解决方案,并且最明显的是参数对未来的维护者来说可能是空的/null。 (我知道 Optional 的设计者打算将其仅用作返回类型,但我找不到在这种情况下不使用它的任何合乎逻辑的理由)。

如果您使用了可选项,您是否不必检查作为参数传递的可选项不是 null 吗?
是的,但是对于将来维护代码的其他人来说,参数可以为空/null,因此可能会避免将来出现空指针异常
理论上可能为空的所有内容就像库中可能调用 System.exit(0) 的每个方法一样。您不能检查 tis,也不应该检查 this。你必须做的所有事情,实际上你应该(几乎)永远不要做。就像将所有参数设为最终参数一样。让您的工具帮助您防止更改参数值或忘记初始化字段,而不是使您的代码无法被数千个 final 和尽可能多的 null 检查所读取。
实际上只需使用 NonNull/Nullable 注释,这就是您在这种情况下要寻找的,而不是可选的。
不太了解 Optional<> 本身可以为 null 的论点。如果您确定在您的代码库中没有空值(在您确保外部库的每个边界中),那么您可以放心没有空值。我在这样的代码库中工作了 2 年,但我们从未获得过 NPE。现在在一个想要使用解决方案 2 的代码库中,我们每隔几周就会得到 NPE,所以它再好不过了,抱歉。我每次都保证解决方案 1。 Scala 也这样做,没有人考虑空值。我也认为 kotlin

A
Anurag

哦,这些编码风格需要加点盐。

(+) 将 Optional 结果传递给另一个方法,不进行任何语义分析;把它留给方法,很好。 (-) 在方法内部使用导致条件逻辑的可选参数实际上是适得其反的。 (-) 需要在 Optional 中打包参数,对于编译器来说是次优的,并且会进行不必要的包装。 (-) 与可为空的参数相比,Optional 的成本更高。 (-) 有人在实际参数中将 Optional 作为 null 传递的风险。

一般来说: Optional 统一了两个必须解开的状态。因此,对于数据流的复杂性,结果比输入更适合。


实际上,具有 Optional 参数表示三种状态之一:null 可选、带有 isPresent() == false 的非空 Optional 和包装实际值的非空 Optional
@biziclop 是的,不可避免的一点已经受到批评。但是 intent 是只有非空表达式。没过多久,就听到了在某些情况下避免使用 Optional 的建议,这很讽刺。一个@NotNull Optional
@biziclop 请注意,如果您完全使用 Optional,则状态 1(null 可选)通常表示编程错误,因此您最好不要处理它(让它抛出 NullPointerException
“编译器的次优”,“与可空参数相比,Optional 成本更高” - 这些参数可能对 C 语言有效,而对 Java 语言无效。 Java 程序员应该关注干净的代码、可移植性、可测试性、良好的架构、模块化等,而不是“可选比空引用更昂贵”。如果你发现你需要专注于微优化,那么你最好跳过对象、列表、泛型并切换到数组和原语(我不想在这里冒犯,我只是分享我的观点) .
“编译器的次优”:过早的优化是万恶之源……如果您不编写性能关键代码(在尝试指定干净的接口时通常是这种情况),这不应该是一个问题。
M
Matthew Simoneau

我在该主题上看到的 best post 是由 Daniel Olszewski 编写的:

尽管对于非强制性方法参数考虑 Optional 可能很诱人,但与其他可能的替代方案相比,这种解决方案显得苍白无力。为了说明问题,请检查以下构造函数声明: public SystemMessage(String title, String content, Optional attachment) { // 分配字段值 } 乍一看,它可能看起来是一个正确的设计决策。毕竟,我们明确地将附件参数标记为可选。但是,对于调用构造函数,客户端代码可能会变得有点笨拙。 SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());附件附件 = 新附件(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment)); Optional 类的工厂方法并没有提供清晰的说明,只会分散读者的注意力。注意只有一个可选参数,但想象一下有两个或三个。 Bob 大叔肯定不会为这样的代码感到自豪😉 当一个方法可以接受可选参数时,最好采用经过充分验证的方法并使用方法重载来设计这种情况。在 SystemMessage 类的示例中,声明两个单独的构造函数优于使用 Optional。 public SystemMessage(String title, String content) { this(title, content, null); } public SystemMessage(String title, String content, Attachment attachment) { // 分配字段值 } 这种变化使客户端代码更简单,更易于阅读。 SystemMessage withoutAttachment = new SystemMessage("title", "content");附件附件 = 新附件(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment);


当只有一两段相关时,这是很多复制+粘贴。
不幸的是,这种解释没有解决调用者何时解析某些信息可能是可选的问题。使用方法重载(如上所述),调用代码必须说,“X 存在吗?如果存在,我将调用重载方法 A。Y 存在吗?我将不得不调用重载方法 B。如果 X 和 Y存在,我将不得不调用重载方法 C。”等等。对此可能有一个很好的答案,但这种“为什么”的解释并没有涵盖它。
此外,当涉及到集合时,空集合和无集合之间存在明显差异。例如,缓存。是缓存未命中吗?空可选/空。是否存在恰好是空集合的缓存命中?完全可选/收藏。
@Ajax 我认为您误解了这篇文章。他们引用了 Effective Java 书中所倡导的观点,该书说,当方法返回类型是 Collection(不是缓存将返回的任意对象)时,您应该倾向于返回空集合而不是 null 或 { 3}。
当然,您应该喜欢它,但您并不总是可以控制某些代码,而且,在非常真实的意义上,您可能想要区分“有一个值并且它是一个空集合”与“没有值已定义(尚未)”。由于“magic null”也是一种不鼓励的做法,因此开发人员可以选择最不坏的选项。我更喜欢空可选来表示缓存未命中而不是空集合,因为实际缓存的值可能是空集合。
L
Lii

几乎没有充分的理由不使用 Optional 作为参数。反对这一点的论点依赖于权威的论点(参见 Brian Goetz - 他的论点是我们不能强制执行非 null 选项)或者 Optional 参数可能为 null(本质上是相同的参数)。当然,Java 中的任何引用都可以为空,我们需要鼓励编译器强制执行规则,而不是程序员的内存(这是有问题的并且无法扩展)。

函数式编程语言鼓励使用 Optional 参数。使用它的最佳方法之一是拥有多个可选参数并使用 liftM2 来使用函数,假设参数不为空并返回可选参数(请参阅 http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html#liftM2-fj.F-)。不幸的是,Java 8 实现了一个非常有限的支持可选的库。

作为 Java 程序员,我们应该只使用 null 与遗留库进行交互。


改用 javax.annotation 中的 @Nonnull@Nullable 怎么样?我在自己开发的库中大量使用它们,IDE(IntelliJ)提供的支持非常好。
这很好,但是你必须在任何地方都使用这个注解,并且你需要一个库来支持诸如 map、flatMap/bind、liftM2、sequence 等方法。
函数式编程语言鼓励可选参数。需要引用。大多数函数不应该是可选的;相反,处理(使用适当的高阶函数)是否存在值的责任在于相关函数的调用者。
这个。事实上,没有一个答案足够令人信服,也没有一个答案能回答这个特定的问题“为什么使用可选参数作为方法参数被认为是不好的做法,而用 @NonNull 注释方法参数是完全可以的,如果它们以不同的方式服务于相同的目的? "对我来说,只有一个论点至少具有某种意义:“应该使用 Optional 来提供具有明确返回类型的更好的 API。但是对于方法参数,可以使用重载函数。” - 不过,我不认为这是一个足够有力的论点。
当然,比 null 更可取,但更可取的是首先不需要很多可选值,因为好的设计:D
d
drew

让我们明确一点:在其他语言中,没有一般建议反对将 Maybe 类型用作字段类型、构造函数参数类型、方法参数类型或函数参数类型。

因此,如果您“不应该”在 Java 中将 Optional 用作参数类型,则原因特定于 Optional、Java 或两者。

可能适用于其他 Maybe 类型或其他语言的推理在这里可能无效。

Brian Goetz

[W] 在添加 [Optional] 时确实有明确的意图,它不是通用的 Maybe 类型,就像很多人希望我们这样做一样。我们的目的是为库方法返回类型提供一种有限的机制,其中需要一种清晰的方式来表示“无结果”,并且使用 null 来表示这种方法极有可能导致错误。例如,您可能永远不应该将它用于返回结果数组或结果列表的东西;而是返回一个空数组或列表。您几乎不应该将它用作某物的字段或方法参数。

所以答案是针对 Optional 的:它不是“通用的 Maybe 类型”;因此,它是有限的,并且可能以限制其作为字段类型或参数类型的有用性的方式受到限制。

也就是说,在实践中,我很少发现使用 Optional 作为字段类型或参数类型是一个问题。如果 Optional 尽管有其限制,但仍可用作您的用例的参数类型或字段类型,请使用它。


与所问主题相关的最佳解释之一
C
Community

带有 Optional 的模式是为了避免返回 null。仍然完全可以将 null 传递给方法。

虽然这些还不是正式的,但您可以使用 JSR-308 style 注释来指示您是否接受 null 值到函数中。请注意,您必须拥有正确的工具才能实际识别它,并且它提供的静态检查比可执行的运行时策略更多,但它会有所帮助。

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

这里的问题是 @NotNull 不是默认情况,必须明确注释 - 因此样板和人们由于懒惰而不会做的事情。
@dwegener 是的,但Optional也不能解决问题,因此我建议使用注释。
忽略 Optional 比忽略只有在阅读文档/源代码或工具可以捕获它时才会注意到的注释要困难得多(静态分析不能总是正确地确定可空性)。
请注意,@Nullable 可能并不意味着您“接受” null 作为有效值,即不抛出任何异常。这可能只是意味着您要防范这种情况,但仍然会抛出异常(这是 Findbugs 语义)。因此,您可以使用此类注释而不是清晰性来引入歧义。
我不确定@user2986984 有什么歧义。 “不处理 null”实际上只能意味着抛出异常。
l
llogiq

该建议是“对输入尽可能不具体,对输出尽可能具体”经验法则的变体。

通常,如果您有一个采用普通非空值的方法,则可以将其映射到 Optional,因此纯版本在输入方面严格来说更加不具体。 然而仍然有很多可能的原因需要您需要 Optional 参数:

您希望您的函数与另一个返回 Optional 的 API 结合使用

如果给定值为空,则您的函数应返回空 Optional 以外的其他内容

您认为 Optional 太棒了,以至于任何使用您的 API 的人都应该被要求了解它;-)


S
Sled

查看JDK10中的JavaDoc,https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html,添加了一个API注释:

API 注意: Optional 主要用作方法返回类型,其中明确需要表示“无结果”,并且使用 null 可能会导致错误。


S
Steve B.

这对我来说似乎有点傻,但我能想到的唯一原因是方法参数中的对象参数在某种程度上已经是可选的——它们可以为空。因此,强迫某人获取现有对象并将其包装在可选项中是毫无意义的。

话虽如此,将获取/返回选项的方法链接在一起是一件合理的事情,例如Maybe monad。


返回值和字段也不能为空吗?所以 Optional 完全没有意义?
g
gpilotino

接受 Optional 作为参数会导致在调用者级别进行不必要的包装。

例如在以下情况下:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

假设您有两个非空字符串(即从其他方法返回):

String p1 = "p1"; 
String p2 = "p2";

即使您知道它们不是空的,您也不得不将它们包装在 Optional 中。

当您必须与其他“可映射”结构组合时,情况会变得更糟,即。 Eithers

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

参考:

方法不应该期望 Option 作为参数,这几乎总是一种代码味道,表明从调用者到被调用者的控制流泄漏,调用者应该负责检查选项的内容

参考。 https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749


E
Eddy

我的看法是 Optional 应该是 Monad,而这些在 Java 中是不可想象的。

在函数式编程中,您处理纯函数和高阶函数,这些函数仅基于其“业务领域类型”来获取和组合它们的参数。组成以现实世界为基础或应向其报告计算的函数(所谓的副作用)需要应用一些函数,这些函数负责自动将值从代表外部世界的单子中解包出来(状态、配置、 Futures、Maybe、Either、Writer 等……);这称为提升。您可以将其视为一种关注点分离。

混合这两个抽象级别不利于易读性,所以你最好避免它。


D
Dmitrii Semikin

也许我会招来一堆反对票和负面评论,但是……我受不了。

免责声明:我在下面写的并不是对原始问题的真正答案,而是我对该主题的看法。它的唯一来源是我的想法和我的经验(使用 Java 和其他语言)。

首先让我们检查一下,为什么会有人喜欢使用 Optional 呢?

对我来说原因很简单:与其他语言不同,java 没有内置的能力来定义变量(或类型)是否可以为空。所有“对象”变量都是可以为空的,所有原始类型都不是。为了简单起见,在进一步讨论中不要考虑原始类型,所以我将简单地声明所有变量都是可以为空的。

为什么需要将变量声明为可空/不可空?嗯,我的理由是:显式总是比隐式更好。除了具有显式装饰(例如注释或类型)之外,还可以帮助静态分析器(或编译器)捕获一些与空指针相关的问题。

许多人在上面的评论中争辩说,函数不需要具有可为空的参数。相反,应该使用重载。但这种说法只在教科书中才有用。在现实生活中有不同的情况。考虑类,它表示某些系统的设置,或某些用户的个人数据,或者实际上是任何包含许多字段的复合数据结构 - 其中许多具有重复的类型,其中一些字段是必需的,而其他字段是可选的.在这种情况下,继承/构造函数重载并没有真正的帮助。

随机示例:假设我们需要收集有关人员的数据。但有些人不想提供所有数据。当然这是 POD,所以基本上用值语义输入,所以我希望它或多或少是不可变的(没有设置器)。

class PersonalData {
    private final String name; // mandatory
    private final int age; // mandatory
    private final Address homeAddress; // optional
    private final PhoneNumber phoneNumber; // optional. Dedicated class to handle constraints
    private final BigDecimal income; // optional.
    // ... further fields

    // How many constructor- (or factory-) overloads do we need to handle all cases
    // without nullable arguments? If I am not mistaken, 8. And what if we have more optional
    // fields?

    // ...
}

因此,上面的 IMO 讨论表明,尽管大多数情况下我们可以在没有可空参数的情况下生存,但有时这并不可行。

现在我们来解决这个问题:如果一些参数可以为空,而另一些则不能,我们怎么知道是哪一个?

方法 1:所有参数都可以为空(根据 java 标准,原始类型除外)。所以我们检查所有这些。

结果:代码因检查而爆炸,这些检查大多是不需要的,因为正如我们上面所讨论的,几乎所有时间我们都可以继续使用可为空的变量,并且只有在极少数情况下才需要“可为空”。

方法 2:使用文档和/或注释来描述哪些参数/字段可以为空,哪些不能。

结果:它并没有真正起作用。人们懒得写和读文档。此外,最近的趋势是,我们应该避免编写文档以使代码本身具有自描述性。除了所有关于修改代码和忘记修改文档的推理仍然有效。

方法 3:@Nullable @NonNull 等...我个人觉得它们很好。但是有一些缺点:(例如,它们只受到外部工具的尊重,而不是编译器),最糟糕的是它们不是标准的,这意味着 1. 我需要在我的项目中添加外部依赖才能受益2. 不同系统对待它们的方式并不统一。据我所知,它们被投票排除在官方 Java 标准之外(我不知道是否有计划再试一次)。

方法 4:可选<>。其他评论中已经提到了缺点,其中最糟糕的是(IMO)性能损失。它还增加了一些样板,即使我个人发现,使用 Optional.empty() 和 Optional.of() 并没有那么糟糕。优点很明显:

它是 Java 标准的一部分。代码的读者(或 API 的用户)很明显,这些参数可能为空。此外,它还强制:API 的用户和方法的开发人员通过显式包装/解包值来了解这一事实(当使用 @Nullable 等注释时,情况并非如此)。

因此,在我看来,包括这一方法在内的任何方法都没有非黑即白之分。我个人最终得到了以下准则和约定(仍然不是严格的规则):

在我自己的代码中,所有变量都必须不为空(但可能是 Optional<>)。如果我有一个带有一个或两个可选参数的方法,我会尝试使用重载、继承等重新设计它。如果我无法在合理的时间内找到解决方案,我会开始思考,如果性能很关键(即如果有数百万个对象待处理)。通常情况并非如此。如果没有,我使用 Optional 作为参数类型和/或字段类型。

仍然存在灰色区域,这些约定不起作用:

我们需要高性能(例如处理大量数据,因此总执行时间非常长,或者吞吐量很关键的情况)。在这种情况下,由 Optional 引入的性能损失可能真的是不需要的。

我们在自己编写的代码的边界上,例如:我们从 DB、Rest Endpoint、解析文件等读取。

或者我们只是使用一些不符合我们约定的外部库,所以再次强调,我们应该小心......

顺便说一句,最后两种情况也可能是可选字段/参数中的需求来源。即当数据的结构不是我们自己开发的,而是由一些外部接口、db-schemas等强加的时候......

最后,我认为,应该考虑正在解决的问题,并尝试找到合适的工具。如果 Optional<> 是合适的,那么我认为没有理由不使用它。

编辑:方法 5:我最近使用了这个,当时我无法使用 Optional。这个想法只是对方法参数和类变量使用命名约定。我使用了“maybe”前缀,这样如果“url”参数可以为空,那么它就变成了maybeUrl。优点是它略微提高了意图的可理解性(并且没有其他方法的缺点,例如外部依赖性或性能损失)。但也有缺点,例如:没有工具支持这种约定(如果您在没有先检查的情况下访问“可能”变量,您的 IDE 不会向您显示任何警告)。另一个问题是,只有在所有从事该项目的人一致应用时,它才会有所帮助。


关于最后一段,我的约定是后缀 OrNull,例如 getUrlOrNull(),用户很清楚此方法可能会返回 null。
P
Pau

传递 Optional 作为参数时要小心的另一个原因是方法应该做一件事......如果传递 Optional 参数,您可能倾向于做不止一件事,它可能类似于传递布尔参数.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

我认为这不是一个很好的理由。当使用非可选参数时,可以应用相同的逻辑来检查参数是否为空。实际上,if(param.isPresent()) 不是使用 Optional 的最佳方法,您可以使用:param.forEach(() -> {...})
我也不喜欢传递可为空的参数的想法。不管怎样,我说你可以赞成,当然也有例外你可以使用它,这取决于你,小心使用它,仅此而已。没有适用于所有场景的规则。
D
Danil Gaponov

我认为这是因为您通常编写函数来操作数据,然后使用 map 和类似函数将其提升到 Optional。这会为其添加默认的 Optional 行为。当然,在某些情况下,您可能需要编写自己的辅助函数以在 Optional 上运行。


M
Macchiatow

我相信存在的原因是您必须首先检查 Optional 本身是否为空,然后尝试评估它包装的值。太多不必要的验证。


传递 null Optional 引用可以被认为是编程错误(因为 Optional 提供了一种传递“空”值的显式方式),因此不值得检查,除非您想避免在所有情况下都抛出异常。
B
Blair

我知道这个问题更多的是关于意见而不是确凿的事实。但我最近从 .net 开发人员转到了 java 开发人员,所以我最近才加入了 Optional 派对。另外,我更愿意将此声明为评论,但由于我的积分水平不允许我发表评论,因此我不得不将其作为答案。

我一直在做的事情,作为一个经验法则,这对我很有帮助。是使用 Optionals 作为返回类型,并且只使用 Optionals 作为参数,如果我需要 Optional 的值,并且天气或 Optional 在方法中没有值。

如果我只关心值,我会在调用方法之前检查 isPresent,如果我在方法中有某种日志记录或不同的逻辑,取决于值是否存在,那么我会很乐意传入 Optional。


l
low_key

在某些涉及 protobuf 或在配置对象中设置字段的用例中,使用 Optional 作为参数可能很有用。

public void setParameters(Optional<A> op1, Optional<B> op2) {
    ProtoRequest.Builder builder = ProtoRequest.newBuilder();
    op1.ifPresent(builder::setOp1);
    op2.ifPresent(builder::setOp2);
...
}

我认为在这种情况下,将可选参数作为参数可能很有用。接收 proto 请求的 API 将处理不同的字段。如果一个函数没有对这些参数进行额外的计算,那么使用 Optional 可能会更简单。

public void setParameters(A op1, B op2) {
    ProtoRequest.Builder builder = ProtoRequest.newBuilder();
    if (op1 != null) {
        builder.setOp1(op1);
    }
    if (op2 != null) {
        builder.setOp2(op2);
    }
...
}

C
Community

正如 Brian Goetz 很好地解释的那样,可选项不是为此目的而设计的。

您始终可以使用 @Nullable 来表示方法参数可以为空。使用可选项并不能真正让您更整洁地编写方法逻辑。


对不起,但我不能同意“不是设计的”或“有人反对它”的论点。作为一名工程师,我们应该具体。使用 @Nullable 比使用 Optional 更糟糕,因为从 API 的角度来看,Optional 更加冗长。我没有看到任何反对使用 Optionals 作为参数的充分理由(前提是您不想使用方法重载)
我错了,还是 @Nullable 不是 java 标准的一部分?据我所知,它被投票否决了...
S
Swaraj Yadav

另一种方法,你可以做的是

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

一旦你建立了一个函数(在这种情况下是供应商),你将能够将它作为任何其他变量传递,并且能够使用它来调用它

calculatedValueSupplier.apply();

这里的想法是您是否有可选值将是您的函数的内部细节,而不是在参数中。在考虑可选参数时考虑函数实际上是我发现的非常有用的技术。

至于您是否应该实际执行此操作的问题取决于您的偏好,但正如其他人所说,至少可以说它使您的 API 变得丑陋。


T
Torsten

起初,我也更喜欢将 Optionals 作为参数传递,但如果您从 API-Designer 视角切换到 API-User 视角,您会看到缺点。

对于您的示例,每个参数都是可选的,我建议将计算方法更改为自己的类,如下所示:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

一个好的 API 设计师总是从用户的角度出发。也许最好说这是 API 实现者 vs API 用户:)
s
speedogoo

这是因为我们对 API 用户和 API 开发人员有不同的要求。

开发人员负责提供精确的规范和正确的实现。因此,如果开发人员已经知道参数是可选的,则实现必须正确处理它,无论它是 null 还是 Optional。 API 对用户来说应该尽可能简单,而 null 是最简单的。

另一方面,结果从 API 开发人员传递给用户。然而,规范是完整而冗长的,用户仍然有可能不知道它或者只是懒于处理它。在这种情况下, Optional 结果迫使用户编写一些额外的代码来处理可能的空结果。


M
MiguelMunoz

首先,如果您使用的是方法 3,您可以将最后 14 行代码替换为:

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

您编写的四种变体是便捷方法。你应该只在它们更方便的时候使用它们。这也是最好的办法。这样,API 就非常清楚哪些成员是必需的,哪些不是。如果你不想写四个方法,你可以通过你如何命名你的参数来澄清事情:

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

这样,很明显允许空值。

您对 p1.orElse(null) 的使用说明了我们的代码在使用 Optional 时会变得多么冗长,这也是我避免使用它的部分原因。 Optional 是为函数式编程编写的。流需要它。除非有必要在函数式编程中使用它们,否则您的方法可能永远不会返回 Optional。有些方法(例如 Optional.flatMap() 方法)需要对返回 Optional 的函数的引用。这是它的签名:

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

所以这通常是编写返回 Optional 的方法的唯一充分理由。但即使在那里,也可以避免。您可以将不返回 Optional 的 getter 传递给 flatMap() 之类的方法,方法是将其包装在另一个将函数转换为正确类型的方法中。包装器方法如下所示:

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

所以假设你有一个像这样的吸气剂:String getName()

你不能像这样将它传递给 flatMap:

opt.flatMap(Widget::getName) // Won't work!

但是你可以像这样传递它:

opt.flatMap(optFun(Widget::getName)) // Works great!

在函数式编程之外,应避免使用 Optional。

Brian Goetz 说得最好:

将 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;

嗯,这就是它被添加到 Java 中的原因之一。还有很多其他的。用对 Optional.map 的单个链接调用序列替换遍历数据结构的嵌套“if”语句的长链是我个人的最爱。除了像这样简单地替换空检查之外,函数式语言编程世界还有许多有趣的 Optional 用途。
D
Dharman

不管 Java 8,使用老式方法重载技术来带来清晰和灵活性,假设您有以下带有两个 args 的方法

public void doSomething(arg1,arg2);

如果你想添加额外的可选参数然后重载方法

public void doSomething(arg1,arg2,arg3) {
Result result = doSomething(arg1,arg2);
// do additional working
}

W
Whimusical

一个很好的例子是可选的,因为参数会很好是 JPA 存储库。我喜欢做 findByNameAndSurname(Optional,Optional) 之类的事情。这样,如果 Optional 为空,则不会执行 WHERE param=y