Java中的可选orElse可选


137

我一直在使用Java 8中的新Optional类型,并且遇到了似乎不受功能支持的常见操作:“ 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的明显忽略。


13
看到这个问题。需要说明的是:这将已经在Java 9中使用-如果不是在Java 8的将来更新中
Obicere,2015年

它是!谢谢,没有在我的搜索中找到。
Yona Appletree'3

2
@Obicere该问题在这里不适用,因为它与空Optional上的行为有关,而不与替代结果有关。可选已经orElseGet()满足了OP的需求,只是它不会生成漂亮的级联语法。
Marko Topolnik


1
有关Java可选的出色
John Detroit

Answers:


86

这是JDK 9的一部分,形式为or,其中包含Supplier<Optional<T>>。您的示例将是:

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

有关详细信息,请参阅Javadoc或我写的这篇文章


真好 那个加成必须是一岁了,我没有注意到。关于您博客中的问题,更改返回类型将破坏二进制兼容性,因为字节码调用指令指的是完整签名,包括返回类型,因此没有机会更改的返回类型ifPresent。但是无论如何,我认为这个名字反而ifPresent是一个好名字。对于在名称中的所有其他方法无法承载“其他”(如mapfilterflatMap),它被暗示他们做什么,如果没有值,何必ifPresent......
霍尔格

因此,添加一种Optional<T> perform(Consumer<T> c)允许链接的方法perform(x).orElseDo(y)orElseDo作为您建议的的替代方法,以可能对缺少的值有所作为的所有方法的名称ifEmpty保持一致else)。您可以perform通过Java 9 来模仿它,stream().peek(x).findFirst()尽管那是对API的滥用,并且在Runnable没有同时指定a 的情况下仍然无法执行Consumer
Holger

65

根据当前的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)就足够了。

由于流的延迟评估,如果先前的服务返回了non-empty,则不会调用任何服务Optional


13
只是在项目中使用过,谢天谢地,尽管我们不进行代码审查。
Ilya Smagin

3
它比“ orElseGet”链要干净得多,但也很难阅读。
slartidan

4
这当然是正确的……但是老实说,我花了一秒钟的时间来解析它,而且我对它的正确性没有信心。提醒您,我意识到这个示例是正确的,但是我可以想象有一个小的更改,使它不会被懒惰地评估,或者有一些其他错误,这些错误一目了然。对我来说,这属于实用功能类别。
Yona Appletree,2015年

3
我想知道是否.map(Optional::get)使用with .findFirst()进行切换将使其更易于“读取”,例如.filter(Optional::isPresent).findFirst().map(Optional::get)可以像“在流中找到Optional :: isPresent为true的第一个元素,然后通过应用Optional :: get使其变平”那样“读取”?
schatten

3
有趣的是,几个月前,我针对类似问题发布了非常相似的解决方案。这是我第一次遇到这个问题。
shmosel

34

它不是很漂亮,但是可以正常工作:

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)

1
呵呵,当我使用这种策略时,eclipse说:“类型为Optional <String>的方法orElseGet(Supplier <?extended String>)不适用于参数(()-> {})”考虑返回一个可选的有效策略,在字符串上?
chrismarx 16/09/23

@chrismarx () -> {}不返回Optional。你想达到什么目的?
Misha

1
我只是想按照这个例子。链接地图调用无效。我的服务返回字符串,然后当然没有.map()选项可用
chrismarx

1
即将到来or(Supplier<Optional<T>>)的Java 9的出色可读替代品
David M.

1
@Sheepy你误会了。 .map()Optional会产生空Optional
米沙


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:使用库

例如,谷歌番石榴的Optional支持适当的or()操作(就像JDK9一样),例如:

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

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


Optional<T>.or(Supplier<Optional<T>>)在Guava文档中找不到。你有一个链接吗?
塔玛斯·赫格杜斯

.orElseGet返回T,但是Optional :: empty返回Optional <T>。Optional.ofNullable(........ orElse(null))具有所需的效果,如@aioobe所述。
米格尔·佩雷拉

@TamasHegedus您的意思是在选项1中?这是一个自定义实现,位于其上方。ps:抱歉,您的回复很晚
绵羊皮的

@MiguelPereira .orElseGet在这种情况下返回可选<T>,因为它的操作上可选<可选<T >>
Sheepy

2

这看起来很适合模式匹配,并且具有Some and None实现(例如JavaslangFunctionalJava中的实现)或cyclops -react中的惰性Maybe实现,这是更传统的Option接口。。我是该库的作者。

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

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

  optional(serviceA(args)).visit(some -> some , 
                                 () -> optional(serviceB(args)).visit(some -> some,
                                                                      () -> serviceC(args)));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.