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

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

有人有更好的主意吗?


当前回答

我想推广用于为函数式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(…)

其他回答

如果你不介意使用第三方库,你可以使用Javaslang。它类似于Scala,但用Java实现。

它带有一个完整的不可变集合库,与Scala中的集合库非常相似。这些集合取代了Java的集合和Java 8的流。它也有自己的Option实现。

import javaslang.collection.Stream;
import javaslang.control.Option;

Stream<Option<String>> options = Stream.of(Option.some("foo"), Option.none(), Option.some("bar"));

// = Stream("foo", "bar")
Stream<String> strings = options.flatMap(o -> o);

下面是第一个问题的解决方案:

import javaslang.collection.Stream;
import javaslang.control.Option;

public class Test {

    void run() {

        // = Stream(Thing(1), Thing(2), Thing(3))
        Stream<Thing> things = Stream.of(new Thing(1), new Thing(2), new Thing(3));

        // = Some(Other(2))
        Option<Other> others = things.flatMap(this::resolve).headOption();
    }

    Option<Other> resolve(Thing thing) {
        Other other = (thing.i % 2 == 0) ? new Other(i + "") : null;
        return Option.of(other);
    }

}

class Thing {
    final int i;
    Thing(int i) { this.i = i; }
    public String toString() { return "Thing(" + i + ")"; }
}

class Other {
    final String s;
    Other(String s) { this.s = s; }
    public String toString() { return "Other(" + s + ")"; }
}

免责声明:我是Javaslang的创造者。

使用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().get();

如果你创建了一个util方法来手动将可选转换为流,你可以摆脱最后一个get():

things.stream()
    .map(this::resolve)
    .flatMap(Util::optionalToStream)
    .findFirst();

如果你从resolve函数中立即返回stream,你就多保存一行。

I'm adding this second answer based on a proposed edit by user srborlongan to my other answer. I think the technique proposed was interesting, but it wasn't really suitable as an edit to my answer. Others agreed and the proposed edit was voted down. (I wasn't one of the voters.) The technique has merit, though. It would have been best if srborlongan had posted his/her own answer. This hasn't happened yet, and I didn't want the technique to be lost in the mists of the StackOverflow rejected edit history, so I decided to surface it as a separate answer myself.

基本上,这种技术是以一种巧妙的方式使用一些可选方法来避免必须使用三元操作符(?:)或if/else语句。

我的内联示例可以这样重写:

Optional<Other> result =
    things.stream()
          .map(this::resolve)
          .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
          .findFirst();

我的例子中使用了一个helper方法,可以这样重写:

/**
 * Turns an Optional<T> into a Stream<T> of length zero or one depending upon
 * whether a value is present.
 */
static <T> Stream<T> streamopt(Optional<T> opt) {
    return opt.map(Stream::of)
              .orElseGet(Stream::empty);
}

Optional<Other> result =
    things.stream()
          .flatMap(t -> streamopt(resolve(t)))
          .findFirst();

评论

让我们直接比较一下原始版本和修改版本:

// original
.flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())

// modified
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))

原来是一个简单的方法:我们得到一个Optional<Other>;如果它有值,则返回包含该值的流,如果它没有值,则返回空流。很简单,也很容易解释。

这种修改很聪明,它的优点是避免了条件语句。(我知道有些人不喜欢三元运算符。如果误用,确实会使代码难以理解。)然而,有时候事情可能太聪明了。修改后的代码还以Optional<Other>开头。然后它调用Optional。映射,定义如下:

如果值存在,则对其应用提供的映射函数,如果结果是非空,则返回描述结果的Optional。否则返回空的Optional。

map(Stream::of)调用返回一个Optional<Stream<Other>>。如果输入Optional中存在一个值,则返回的Optional包含一个包含单个Other结果的流。但如果该值不存在,则结果为空的Optional。

接下来,调用orElseGet(Stream::empty)返回类型为Stream<Other>的值。如果它的输入值是存在的,它得到的值是单个元素Stream<Other>。否则(如果输入值不存在)它返回一个空的Stream<Other>。因此,结果是正确的,与原始条件代码相同。

在对我的回答的评论中,关于被拒绝的编辑,我把这种方法描述为“更简洁,但也更模糊”。我坚持这一点。我花了一段时间才弄清楚它在做什么,也花了一段时间写了上面关于它在做什么的描述。关键的微妙之处在于从Optional<Other>到Optional<Stream<Other>>的转换。一旦你明白了这一点,它就说得通了,但对我来说并不明显。

不过,我承认,随着时间的推移,最初晦涩的东西可能会变成惯用用语。这种技术最终可能成为实践中最好的方法,至少在可选之前是这样。流被添加(如果曾经添加过的话)。

更新:可选的。流已添加到JDK 9。