在 Java 8 中链接可选

Chaining Optionals in Java 8

正在寻找一种链接可选值的方法,以便返回第一个存在的选项。如果 none 存在,则应返回 Optional.empty()

假设我有几个这样的方法:

Optional<String> find1()

我正在尝试链接它们:

Optional<String> result = find1().orElse( this::find2 ).orElse( this::find3 );

但这当然行不通,因为 orElse 需要一个值,而 orElseGet 需要一个 Supplier

你可以这样做:

Optional<String> resultOpt = Optional.of(find1()
                                .orElseGet(() -> find2()
                                .orElseGet(() -> find3()
                                .orElseThrow(() -> new WhatEverException()))));

虽然我不确定它是否提高了 IMO 的可读性。 Guava 提供了一种链接 Optionals 的方法:

import com.google.common.base.Optional;

Optional<String> resultOpt = s.find1().or(s.find2()).or(s.find3());

它可能是您问题的另一种选择,但在 JDK.

中不使用标准可选 class

如果你想保持标准API,你可以写一个简单的实用方法:

static <T> Optional<T> or(Optional<T> first, Optional<T> second) {
    return first.isPresent() ? first : second;
}

然后:

Optional<String> resultOpt = or(s.find1(), or(s.find2(), s.find3()));

如果你有很多可选链,也许最好使用 Stream 方法,就像其他已经提到的那样。

使用流:

Stream.of(find1(), find2(), find3())
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

如果您需要延迟计算查找方法,请使用供应商函数:

Stream.of(this::find1, this::find2, this::find3)
    .map(Supplier::get)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

可能是

之一
    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, @SuppressWarnings("unchecked") Supplier<Optional<? extends T>>... supp) {
        if (first.isPresent()) return first;
        for (Supplier<Optional <? extends T>> sup : supp) {
            Optional<? extends T> opt = sup.get();
            if (opt.isPresent()) {
                return opt;
            }
        }
        return Optional.empty();
    }

    public <T> Optional<? extends T> firstOf(Optional<? extends T> first, Stream<Supplier<Optional<? extends T>>> supp) {
        if (first.isPresent()) return first;
        Stream<Optional<? extends T>> present = supp.map(Supplier::get).filter(Optional::isPresent);
        return present.findFirst().orElseGet(Optional::empty);
    }

会做。

第一个遍历一组供应商。第一个非空 Optional<> 是 returned。如果找不到,我们 return 一个空的 Optional

第二个对遍历 SuppliersStream 做同样的事情,每个人(懒惰地)询问它们的值,然后过滤空的 Optionals .第一个非空的是 returned,或者如果不存在,则为空的。

受 Sauli 的回答启发,可以使用 flatMap() 方法。

Stream.of(this::find1, this::find2, this::find3)
  .map(Supplier::get)
  .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
  .findFirst();

将 Optional 转换为 Stream 很麻烦。显然,这将是 fixed with JDK9。所以这可以写成

Stream.of(this::find1, this::find2, this::find3)
  .map(Supplier::get)
  .flatMap(Optional::stream)
  .findFirst();

Java9发布后更新

虽然原题是关于Java8的,但是在Java9中引入了Optional::or,有了它,问题可以如下解决

Optional<String> result = find1()
  .or(this::find2)
  .or(this::find3);

执行可选链接首先转换流到可选使用两种方法之一

  1. findAny() 或 findFirst()
  2. 最小值() / 最大值()

一旦获得可选的,可选的还有两个实例方法,它们也存在于 Stream class 中,即 filter 和 map()。 在方法上使用这些并检查输出使用 ifPresent(System.out :: Println)

例如:

流 s = Stream.of(1,2,3,4);

s.findFirst().filter((a)->a+1).ifPresent(System.out :: Println)

输出为:2

基于 Alexis C 的回答,但没有嵌套 orElses

String result = find1()
                   .map(Optional::of)
                   .orElseGet(Foo::find2())
                   .map(Optional::of)
                   .orElseGet(Foo::find3())
                   .orElseThrow(() -> new WhatEverException())

如果您想要 Optional<String> 作为结果,请删除 orElseThrow

诀窍是在每个 orElseGet.

之前将 findX 返回的每个可选包装到另一个可选

用于级联链接您可以使用 ifPresentOrElse

find1().ifPresentOrElse( System.out::println, new Runnable() {
  public void run() {
    find2().ifPresentOrElse( System.out::println, new Runnable() {
      public void run() {
        find3().ifPresentOrElse( System.out::println, new Runnable() {
          public void run() {
            System.err.println( "nothing found…" );
          }
        } );
      }
    } );
  }
} );

要用 Optional 的值做某事你必须用你的 Consumer
替换 System.out::println (此解决方案中也可能有不同的消费者)

// Java 9+
find1().or(() -> find2()).or(() -> find3());


// Java 8
Optional.ofNullable(
   find1().orElse(
      find2().orElse(
         find3().orElse( null )
)));

我对所有这些问题的通用通用解决方案:

public static <T> T firstMatch(final Predicate<T> matcher, final T orElse, final T... values) {
  for (T t : values) {
    if (matcher.test(t)) {
      return t;
    }
  }
  return orElse;
}

那么你可以这样做:

public static <T> Optional<T> firstPresent(final Optional<T>... values) {
  return firstMatch(Optional::isPresent, Optional.empty(), values);
}

自 Java 9

读者正在寻找的最有可能的案例(今天)

result = find1()
    .or(this::find2)
    .or(this::find3);

Java 8

result = Optional.ofNullable(find1()
    .orElse(find2()
      .orElse(find3()
        .orElse(null))));