Java 8中的可选链接


68

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

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

Optional<String> find1()

我正在尝试将它们链接起来:

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

但是当然这是行不通的,因为orElse期望一个值并且orElseGet期望一个Supplier


3
这需要一个版本Supplier.orElseGet()
glglgl 2015年

Answers:


93

使用流:

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();

1
如果找不到任何项目,则findFirst()实际上返回一个空的可选内容,因此我将删除orElse()。
SauliTähkäpää2015年

1
您的比我的还要好-除可读性外:amybe的第一个map可以更改为.map(Supplier::get),第二个可以更改为.map(Optional::get)。当您使用它时,.filter(Optional::isPresent)也可以,但是不会像其他方法那样损害可读性。
glglgl 2015年

7
尽管通过很多代码可以解决一个简单的问题,但这仍然有效。让我认为它应该内置在可选api
堆纸机中2015年

6
Optional中肯定缺少的一件事是转换器到Stream的转换,而不仅仅是针对此用例。然后,这将更易于管理:Stream.of(opt1,opt2,opt3).flatMap(Optional::stream).findFirst()
Marko Topolnik

2
要带参数的方法...连锁自选- > stackoverflow.com/questions/28818506/...
尼克Grealy

40

您可以这样做:

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

尽管我不确定它是否可以提高IMO的可读性。番石榴提供了一种链接可选项的方法:

import com.google.common.base.Optional;

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

它可能是您问题的另一种替代方法,但未在JDK中使用标准的Optional类。

如果要保留标准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方法,就像已经提到的其他方法一样。


3
使用实用程序方法的解决方案可能会尽可能地易读。太糟糕了,Java的Optional没有和Guava one相同的方法
堆积器

4
or(Optional<T>... opts)会有所改善。但是,带有泛型的Varargs是不幸的混合。
Marko Topolnik

1
您的第一种方法可能会引发异常,即使find1find2将返回一个值。
更好的Oliver

2
@zeroflagL是的,在这种情况下,它将抛出一个新的WhatEverException(OP最初并未确定在这种情况下他将具有一个空的可选内容)。虽然我不建议您使用这种方法,但是不确定是否可以一站式完成。
Alexis C.

2
也许有误会。如果find1返回一个值(= non-empty Optional),则返回值find3无所谓。但是,尽管我们已经有了一个有效的结果,但是在您的第一个解决方案中可能会引发异常。换句话说:理想情况下find3不应该首先调用它。
更好的Oliver,2015年

34

受到索利(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很麻烦。显然,这将通过JDK9修复。所以这可以写成

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

Java 9发布后的更新

尽管最初的问题是关于Java 8的,Optional::or但它是在Java 9中引入的。使用它,可以按以下方式解决问题

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

0

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

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

orElseThrow如果要Optional<String>作为结果,则删除。

诀窍是将传回的每个可选选项包装到每个findX之前的另一个可选选项orElseGet


0

级联链接,您可以使用 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您”的价值来做某事,必须System.out::println用“您”替换“您” Consumer
(此解决方案中也可以使用不同的消费者)


-2

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

  1. findAny()或findFirst()
  2. min()/ max()

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

例如:

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

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

输出为:2


1
第一:filter方法必须返回一个布尔值。“不兼容的类型:lambda表达式int中的错误返回类型无法转换为布尔值”
user2814648

1
当输入为空时,您将获得一个NPE。Stream<Integer> s = Stream.of(null,2,3,4); s.findFirst().map((a)->a+1).ifPresent(System.out::println);
user2814648'18年

-4

也许是其中之一

    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<>返回第一个非空。如果找不到,则会返回一个空值Optional

第二个相同StreamSuppliers但遍历一个,则每个(懒惰地)询问它们的值,然后将其过滤为空Optionals。返回第一个非空的,如果不存在,则返回一个空的。


1
哇,再次通知-1 w / o有什么可以改进的地方。
glglgl

3
因为上面的答案以更加清晰和优雅的方式完全相同。
Innokenty
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.