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。澄清一下:这已经在 Java 9 中了——如果不是在 Java 8 的未来更新中的话。 是的!谢谢,在我的搜索中没有找到。 @Obicere 这个问题在这里不适用,因为它是关于空可选的行为,而不是关于替代结果。 Optional 已经有orElseGet()
来满足 OP 的需要,只是它不会生成好的级联语法。
***.com/questions/23773024/…
很棒的 Java 教程 可选:codeflex.co/java-optional-no-more-nullpointerexception
【参考方案1】:
这是or
形式的JDK 9 的一部分,它采用Supplier<Optional<T>>
。您的示例将是:
return serviceA(args)
.or(() -> serviceB(args))
.or(() -> serviceC(args));
详情见the Javadoc或this post我写的。
【讨论】:
不错。那个添加必须是一岁,我没有注意到。关于你博客中的问题,更改返回类型会破坏二进制兼容性,因为字节码调用指令是指完整的签名,包括返回类型,所以没有机会更改ifPresent
的返回类型。但无论如何,我认为ifPresent
这个名字无论如何都不是一个好名字。对于名称中不包含“else”的所有其他方法(如map
、filter
、flatMap
),暗示如果不存在任何值,它们什么也不做,那么为什么ifPresent
...
因此添加一个Optional<T> perform(Consumer<T> 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();
重要的方面不是您必须编写一次的(恒定的)操作链,而是添加另一个服务(或修改一般的服务列表)有多容易。在这里,添加或删除单个()->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<Result>
。如果Optional
包含值v
,我们想要得到Optional.of(v)
,但如果它是空的,我们想要得到serviceB(args)
。用更多替代品重复冲洗。
这种模式的其他用途是
.map(Stream::of).orElseGet(Stream::empty)
.map(Collections::singleton).orElseGet(Collections::emptySet)
【讨论】:
呵呵,当我使用这个策略时,eclipse 说:“类型 Optional() ->
不返回 Optional
。你想完成什么?
我只是想效仿这个例子。链接地图调用不起作用。我的服务返回字符串,当然没有可用的 .map() 选项
Java 9 即将推出的 or(Supplier<Optional<T>>)
的优秀可读替代品
@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<T>.or(Supplier<Optional<T>>)
。你有那个链接吗?
.orElseGet 返回 T 但 Optional::empty 返回 Optional这看起来很适合模式匹配和更传统的 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 可选的主要内容,如果未能解决你的问题,请参考以下文章