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

Posted

技术标签:

【中文标题】为啥不应该在参数中使用 Java 8 的 Optional【英文标题】:Why should Java 8's Optional not be used in arguments为什么不应该在参数中使用 Java 8 的 Optional 【发布时间】:2015-11-02 13:30:24 【问题描述】:

我在许多网站上阅读过 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

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

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

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

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

【讨论】:

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

我在这个话题上看到的best post是Daniel Olszewski写的:

尽管将 Optional 用于非强制性方法参数可能很诱人,但与其他可能的替代方案相比,这种解决方案显得苍白无力。为了说明问题,请检查以下构造函数声明:

public SystemMessage(String title, String content, Optional<Attachment> attachment) 
    // assigning field values

乍一看,这可能是一个正确的设计决策。毕竟我们 将附件参数显式标记为可选。然而,作为 对于调用构造函数,客户端代码可以变成一点点 笨拙。

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

Optional 的工厂方法并没有提供清晰的说明 课堂只会分散读者的注意力。注意只有一个可选 参数,但想象一下有两个或三个。鲍伯叔叔肯定 不会为这样的代码感到自豪?

当一个方法可以接受可选参数时,最好采用经过充分验证的方法,并使用方法设计这种情况 重载。 在 SystemMessage 类的示例中,声明 两个独立的构造函数优于使用 Optional。

public SystemMessage(String title, String content) 
    this(title, content, null);


public SystemMessage(String title, String content, Attachment attachment) 
    // assigning field values

该更改使客户端代码更加简单易读。

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

【讨论】:

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

几乎没有充分的理由不使用Optional 作为参数。反对这一点的论点依赖于权威的论点(参见 Brian Goetz - 他的论点是我们不能强制执行非空选项)或者 Optional 参数可能为空(本质上是相同的参数)。当然,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【参考方案4】:

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

因此,如果您“不应该”在 Java 中使用 Optional 作为参数类型,原因是 特定于 对 Optional、Java 或两者都有。

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

每Brian Goetz,

[W]e 确实有一个明确的 添加[可选]时的意图,并且不是通用的 目的也许类型,就像很多人希望我们做的那样 所以。我们的目的是为图书馆提供一个有限的机制 方法返回类型,需要用一种清晰的方式来表示 “没有结果”,并且对此类使用 null 极有可能 导致错误。

例如,您可能永远不应该将它用于 返回结果数组或结果列表;而是返回一个 空数组或列表。你几乎不应该把它当作一个领域 某物或方法参数。

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

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

【讨论】:

与所问主题相关的最佳解释之一【参考方案5】:

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”实际上只能意味着抛出异常。【参考方案6】:

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

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

您希望您的函数与另一个返回 Optional 的 API 结合使用 如果给定值为空,您的函数应返回空 Optional 以外的其他内容 您认为Optional 太棒了,应该要求使用您的 API 的人了解它;-)

【讨论】:

【参考方案7】:

查看JDK10中的JavaDoc,https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html,添加API说明:

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

【讨论】:

【参考方案8】:

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

话虽如此,将获取/返回选项的方法链接在一起是合理的做法,例如也许是单子。

【讨论】:

不能返回值和字段为空吗?所以Optional 完全没有意义?【参考方案9】:

接受 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 作为参数,这几乎总是 代码气味表明调用者的控制流泄漏到 被调用者,调用者应该负责检查 Option的内容

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

【讨论】:

【参考方案10】:

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

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

混合这两个抽象级别不利于易读性,因此最好避免使用它。

【讨论】:

【参考方案11】:

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

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

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

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

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

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

随机示例:假设我们需要收集有关人员的数据。但有些人不想提供所有数据。当然这是 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:使用文档和/或 cmets 来描述,哪些参数/字段可以为空,哪些不能。

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

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

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

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

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

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

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

我们需要高性能(例如处理大量数据,因此总执行时间非常长,或者吞吐量很关键的情况)。在这种情况下,Optional 引入的性能损失可能是不受欢迎的。 我们处于代码的边界,我们自己编写代码,例如:我们从 DB、Rest Endpoint、解析文件等读取。 或者我们只是使用一些外部库,它们不符合我们的约定,所以再次提醒我们应该小心...

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

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

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

【讨论】:

关于最后一段,我的约定是后缀OrNull,例如getUrlOrNull(),用户很清楚这种方法可能会返回一个空值。【参考方案12】:

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

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

【讨论】:

我认为这不是一个好的理由。当使用非可选参数时,可以应用相同的逻辑来检查参数是否为空。实际上,if(param.isPresent()) 并不是使用 Optional 的最佳方法,您可以使用:param.forEach(() -&gt; ...) 我也不喜欢传递可空参数的想法。反正我说了你可以赞成,当然也有例外你可以用,看你自己,小心用,仅此而已。没有适用于所有场景的规则。【参考方案13】:

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

【讨论】:

【参考方案14】:

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

【讨论】:

传递一个空的可选引用可以被认为是一个编程错误(因为可选提供了一个传递“空”值的显式方式),所以它不值得检查,除非你希望在所有情况下都避免抛出异常。【参考方案15】:

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

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

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

【讨论】:

【参考方案16】:

选项不是为此目的而设计的,Brian Goetz 很好地解释了这一点。

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

【讨论】:

很抱歉,但我不能同意“不是设计的”或“有人反对它”的说法。作为一名工程师,我们应该具体。使用 @Nullable 比使用 Optional 更糟糕,因为从 API 的角度来看,Optional 更加冗长。我没有看到任何反对使用 Optionals 作为参数的充分理由(前提是您不想使用方法重载) 我错了,还是 @Nullable 不是 java 标准的一部分?据我记得它被否决了......【参考方案17】:

还有一种方法,你可以做的是

// 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 变得丑陋。

【讨论】:

【参考方案18】:

起初,我也更喜欢将 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 用户:)【参考方案19】:

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

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

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

【讨论】:

【参考方案20】:

首先,如果您使用的是方法 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 &lt;U&gt; Optional&lt;U&gt; flatMap(Function&lt;? super T, ? extends Optional&lt;? extends U&gt;&gt; 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”语句的长链是我个人的最爱。除了像这样简单地替换空检查之外,函数式语言编程世界还有许多有趣的用途。【参考方案21】:

在某些涉及 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);
    
...

【讨论】:

以上是关于为啥不应该在参数中使用 Java 8 的 Optional的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ?

为啥我们不应该在 py 脚本中使用 sys.setdefaultencoding("utf-8") ?

new java.util.Date() 得到的时间与系统时间不一样,为啥?

为啥 Java 的基于值的类不应该被序列化?

C++:调用无参数的构造函数为啥不加括号

为啥应该设置 com.sun.management.jmxremote.rmi.port?