新的Java 8流框架及其朋友可以编写一些非常简洁的Java代码,但我遇到过一个看似简单的情况,但要做到简洁却很棘手。

考虑一个列表<Thing> things and method Optional<Other> resolve(Thing Thing)。我想映射的东西可选<其他>s,并获得第一个其他。

显而易见的解决方案是使用things.stream().flatMap(this::resolve). findfirst(),但flatMap要求您返回一个流,而Optional没有stream()方法(或者它是一个集合或提供一个方法将其转换为或将其视为一个集合)。

我能想到的最好的是:

things.stream()
    .map(this::resolve)
    .filter(Optional::isPresent)
    .map(Optional::get)
    .findFirst();

但对于一个很普通的例子来说,这似乎太啰嗦了。

有人有更好的主意吗?


当前回答

流提供的我的库abacus-common支持Null。下面是代码:

Stream.of(things).map(e -> resolve(e).orNull()).skipNull().first();

其他回答

如果你只能使用Java 8,但可以访问Guava 21.0或更新版本,你可以使用Streams。流将可选项转换为流。

因此,鉴于

import com.google.common.collect.Streams;

你可以写

Optional<Other> result =
    things.stream()
        .map(this::resolve)
        .flatMap(Streams::stream)
        .findFirst();

使用reduce的略短版本:

things.stream()
  .map(this::resolve)
  .reduce(Optional.empty(), (a, b) -> a.isPresent() ? a : b );

你也可以将reduce函数移动到一个静态实用程序方法,然后它变成:

  .reduce(Optional.empty(), Util::firstPresent );

那个怎么样?

private static List<String> extractString(List<Optional<String>> list) {
    List<String> result = new ArrayList<>();
    list.forEach(element -> element.ifPresent(result::add));
    return result;
}

https://stackoverflow.com/a/58281000/3477539

由于我之前的答案似乎不太受欢迎,我将再试一次。

简短的回答:

你基本上是在正确的轨道上。得到你想要的输出的最短代码是这样的:

things.stream()
      .map(this::resolve)
      .filter(Optional::isPresent)
      .findFirst()
      .flatMap( Function.identity() );

这将符合您的所有要求:

它将找到解析为非空可选<Result>的第一个响应 它会根据需要惰性地调用::resolve 这个::resolve不会在第一个非空结果之后被调用 它将返回Optional<Result>

再回答

与OP初始版本相比,唯一的修改是我在调用. findfirst()之前删除了.map(可选::get),并添加了. flatmap (o -> o)作为链中的最后一个调用。

当流找到一个实际的结果时,这有一个很好的效果,可以消除double-Optional。

在Java中没有比这个更短的了。

使用更传统的for循环技术的替代代码片段将有相同的代码行数,并且有或多或少相同的顺序和你需要执行的操作数量:

调用this.resolve, 基于Optional.isPresent进行过滤 返回结果和 处理消极结果的方法(当什么都没有发现时)

为了证明我的解决方案像宣传的那样有效,我写了一个小测试程序:

public class StackOverflow {

    public static void main( String... args ) {
        try {
            final int integer = Stream.of( args )
                    .peek( s -> System.out.println( "Looking at " + s ) )
                    .map( StackOverflow::resolve )
                    .filter( Optional::isPresent )
                    .findFirst()
                    .flatMap( o -> o )
                    .orElseThrow( NoSuchElementException::new )
                    .intValue();

            System.out.println( "First integer found is " + integer );
        }
        catch ( NoSuchElementException e ) {
            System.out.println( "No integers provided!" );
        }
    }

    private static Optional<Integer> resolve( String string ) {
        try {
            return Optional.of( Integer.valueOf( string ) );
        }
        catch ( NumberFormatException e )
        {
            System.out.println( '"' + string + '"' + " is not an integer");
            return Optional.empty();
        }
    }

}

(它确实有一些额外的行来调试和验证,只有尽可能多的调用来解决需要的…)

在命令行上执行这个命令,我得到了以下结果:

$ java StackOferflow a b 3 c 4
Looking at a
"a" is not an integer
Looking at b
"b" is not an integer
Looking at 3
First integer found is 3

我想推广用于为函数式api创建helper的工厂方法:

Optional<R> result = things.stream()
        .flatMap(streamopt(this::resolve))
        .findFirst();

工厂方法:

<T, R> Function<T, Stream<R>> streamopt(Function<T, Optional<R>> f) {
    return f.andThen(Optional::stream); // or the J8 alternative:
    // return t -> f.apply(t).map(Stream::of).orElseGet(Stream::empty);
}

推理:

与一般的方法引用相比,与lambda表达式相比,你不能意外地从可访问范围中捕获变量,例如: T -> streamopt(resolve(o)) 它是可组合的,你可以调用Function::and然后在工厂方法result上: streamopt(::解决).andThen(…) 而在lambda的情况下,你需要先强制转换: ((T <函数,流< R > >) T - > streamopt(解决(T))) .andThen(…)