Java 中的可选 orElse 可选

Posted

技术标签:

【中文标题】Java 中的可选 orElse 可选【英文标题】:Optional orElse Optional in Java 【发布时间】:2015-05-03 07:39:48 【问题描述】:

我一直在使用新的Optional type in Java 8,并且遇到了一个功能上不支持的看似常见的操作:“orElseOptional”

考虑以下模式:

Optional<Result> resultFromServiceA = serviceA(args);
if (resultFromServiceA.isPresent) return result;
else 
    Optional<Result> resultFromServiceB = serviceB(args);
    if (resultFromServiceB.isPresent) return resultFromServiceB;
    else return serviceC(args);

这种模式有很多种形式,但归根结底是在一个可选项上需要一个“orElse”,它接受一个产生新可选项的函数,只有在当前选项不存在时才调用。

它的实现如下所示:

public Optional<T> orElse(Supplier<Optional<? extends T>> otherSupplier) 
    return value != null ? this : other.get();

我很好奇这样的方法不存在是否有原因,如果我只是以无意的方式使用 Optional,以及人们想出什么其他方法来处理这种情况。

我应该说我认为涉及自定义实用程序类/方法的解决方案并不优雅,因为使用我的代码的人不一定知道它们存在。

另外,如果有人知道,JDK 9 中会包含这样的方法吗?我在哪里可以提出这样的方法?对我来说,这似乎是 API 的一个非常明显的遗漏。

【问题讨论】:

See this issue。澄清一下:这已经在 J​​ava 9 中了——如果不是在 Java 8 的未来更新中的话。 是的!谢谢,在我的搜索中没有找到。 @Obicere 这个问题在这里不适用,因为它是关于空可选的行为,而不是关于替代结果。 Optional 已经有 orElseGet() 来满足 OP 的需要,只是它不会生成好的级联语法。 ***.com/questions/23773024/… 很棒的 Java 教程 可选:codeflex.co/java-optional-no-more-nullpointerexception 【参考方案1】:

这是or 形式的JDK 9 的一部分,它采用Supplier&lt;Optional&lt;T&gt;&gt;。您的示例将是:

return serviceA(args)
    .or(() -> serviceB(args))
    .or(() -> serviceC(args));

详情见the Javadoc或this post我写的。

【讨论】:

不错。那个添加必须是一岁,我没有注意到。关于你博客中的问题,更改返回类型会破坏二进制兼容性,因为字节码调用指令是指完整的签名,包括返回类型,所以没有机会更改ifPresent的返回类型。但无论如何,我认为ifPresent 这个名字无论如何都不是一个好名字。对于名称中不包含“else”的所有其他方法(如mapfilterflatMap),暗示如果不存在任何值,它们什么也不做,那么为什么ifPresent... 因此添加一个Optional&lt;T&gt; perform(Consumer&lt;T&gt; c) 方法以允许链接perform(x).orElseDo(y)orElseDo 作为您建议的ifEmpty 的替代方案,以保持else 以所有方法的名称可能会为缺失值做点什么)。您可以通过 stream().peek(x).findFirst() 在 Java 9 中模仿 perform,尽管这是对 API 的滥用,并且如果不同时指定 Consumer,仍然无法执行 Runnable...【参考方案2】:

在当前 API 的情况下,最简洁的“试用服务”方法是:

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.map(Supplier::get)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();

重要的方面不是您必须编写一次的(恒定的)操作链,而是添加另一个服务(或修改一般的服务列表)有多容易。在这里,添加或删除单个()-&gt;serviceX(args) 就足够了。

由于流的延迟评估,如果前面的服务返回非空Optional,则不会调用任何服务。


从 Java 9 开始,您可以将代码简化为

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.flatMap(s -> s.get().stream())
.findFirst();

虽然this answer 已经包含一个更简单的 JDK 9 方法。

JDK 16 提供了替代方案

Optional<Result> o = Stream.<Supplier<Optional<Result>>>of(
    ()->serviceA(args), 
    ()->serviceB(args), 
    ()->serviceC(args), 
    ()->serviceD(args))
.<Result>mapMulti((s,c) -> s.get().ifPresent(c))
.findFirst();

虽然这种方法对于接受 Consumer 而不是返回 Supplier 的服务方法可能更方便。

【讨论】:

刚刚在项目中使用过,感谢上帝,我们不进行代码审查。 它比“orElseGet”链更简洁,但也更难阅读。 这当然是正确的......但老实说,我需要一秒钟来解析它,而且我不相信它是正确的。请注意,我意识到这个例子是,但我可以想象一个小的变化,它不会被懒惰地评估或有一些其他的错误,一目了然。对我来说,这属于实用功能类别。 我想知道将.map(Optional::get).findFirst() 切换是否会更容易“阅读”,例如.filter(Optional::isPresent).findFirst().map(Optional::get) 可以像“在流中找到 Optional::isPresent 为真的第一个元素,然后通过应用 Optional::get 将其展平”一样“读取”? 有趣的是,几个月前我发布了一个非常相似的solution 用于类似的问题。这是我第一次遇到这种情况。【参考方案3】:

它不漂亮,但这会起作用:

return serviceA(args)
  .map(Optional::of).orElseGet(() -> serviceB(args))
  .map(Optional::of).orElseGet(() -> serviceC(args))
  .map(Optional::of).orElseGet(() -> serviceD(args));

.map(func).orElseGet(sup) 是一个相当方便的模式,可与Optional 一起使用。它的意思是“如果这个Optional 包含值v,给我func(v),否则给我sup.get()”。

在这种情况下,我们调用serviceA(args) 并获得Optional&lt;Result&gt;。如果Optional 包含值v,我们想要得到Optional.of(v),但如果它是空的,我们想要得到serviceB(args)。用更多替代品重复冲洗。

这种模式的其他用途是

.map(Stream::of).orElseGet(Stream::empty) .map(Collections::singleton).orElseGet(Collections::emptySet)

【讨论】:

呵呵,当我使用这个策略时,eclipse 说:“类型 Optional 中的方法 orElseGet(Supplier extends String>) 不适用于参数 (() -> )" 它似乎没有考虑在 String 上返回 Optional 一个有效的策略?? @chrismarx () -&gt; 不返回 Optional。你想完成什么? 我只是想效仿这个例子。链接地图调用不起作用。我的服务返回字符串,当然没有可用的 .map() 选项 Java 9 即将推出的 or(Supplier&lt;Optional&lt;T&gt;&gt;) 的优秀可读替代品 @Sheepy 你错了。 .map() 在一个空的Optional 上会产生一个空的Optional【参考方案4】:

也许这就是你所追求的:Get value from one Optional or another

否则,您可能想看看Optional.orElseGet。这是我认为你所追求的一个例子:

result = Optional.ofNullable(serviceA().orElseGet(
                                 () -> serviceB().orElseGet(
                                     () -> serviceC().orElse(null))));

【讨论】:

这是天才通常会做的事情。将可选项评估为可空值并用 ofNullable 包装它是我见过的最酷的事情。【参考方案5】:

假设您仍在使用 JDK8,则有多种选择。

选项#1:制作自己的辅助方法

例如:

public class Optionals 
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) 
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    

这样你就可以做到:

return Optionals.or(
   ()-> serviceA(args),
   ()-> serviceB(args),
   ()-> serviceC(args),
   ()-> serviceD(args)
);

选项#2:使用库

例如google guava 的 Optional 支持正确的or() 操作(就像JDK9一样),例如:

return serviceA(args)
  .or(() -> serviceB(args))
  .or(() -> serviceC(args))
  .or(() -> serviceD(args));

(每个服务都返回com.google.common.base.Optional,而不是java.util.Optional)。

【讨论】:

我在 Guava 文档中没有找到 Optional&lt;T&gt;.or(Supplier&lt;Optional&lt;T&gt;&gt;)。你有那个链接吗? .orElseGet 返回 T 但 Optional::empty 返回 Optional。 Optional.ofNullable(........orElse(null)) 具有@aioobe 所述的预期效果。 @TamasHegedus 你的意思是选项#1?这是一个自定义实现,位于其上方。 ps:抱歉回复晚了 @MiguelPereira .orElseGet 在这种情况下返回 Optional 因为它在 Optional> 上运行【参考方案6】:

这看起来很适合模式匹配和更传统的 Option 接口,带有 Some 和 None 实现(例如 Javaslang、FunctionalJava 中的那些)或 cyclops-react.I 中的惰性 Maybe 实现我是这个图书馆的作者。

使用cyclops-react,您还可以在JDK 类型上使用结构化pattern matching。对于 Optional,您可以通过 visitor pattern 匹配当前和缺席案例。它看起来像这样 -

  import static com.aol.cyclops.Matchables.optional;

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));

【讨论】:

以上是关于Java 中的可选 orElse 可选的主要内容,如果未能解决你的问题,请参考以下文章

jquery Combo Select 下拉框可选可输入插件

可选类中的可选属性 VS 可选字典中的可选值

Java 接口中的可选方法

Java 构造函数 - 子类构造函数中的可选参数

Swift 中的可选值是啥?

JavaScript中的可选参数[重复]