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

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

有人有更好的主意吗?


当前回答

你不能做得更简洁,因为你已经在做了。

你声称你不想要.filter(可选::isPresent)和.map(可选::get)。

这个问题已经通过@StuartMarks描述的方法解决了,但是结果是你现在将它映射到Optional<T>,所以现在你需要使用. flatmap (This::streamopt)和get()在最后。

所以它仍然由两个语句组成,现在您可以使用新方法获得异常!因为,如果每个可选项都是空的呢?然后findFirst()将返回一个空的可选项,get()将失败!

那么你得到的是:

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

实际上是实现你想要的最好的方法,那就是你想要将结果保存为T,而不是Optional<T>。

我擅自创建了一个CustomOptional<T>类,它包装了Optional<T>,并提供了一个额外的方法flatStream()。注意你不能扩展Optional<T>:

class CustomOptional<T> {
    private final Optional<T> optional;

    private CustomOptional() {
        this.optional = Optional.empty();
    }

    private CustomOptional(final T value) {
        this.optional = Optional.of(value);
    }

    private CustomOptional(final Optional<T> optional) {
        this.optional = optional;
    }

    public Optional<T> getOptional() {
        return optional;
    }

    public static <T> CustomOptional<T> empty() {
        return new CustomOptional<>();
    }

    public static <T> CustomOptional<T> of(final T value) {
        return new CustomOptional<>(value);
    }

    public static <T> CustomOptional<T> ofNullable(final T value) {
        return (value == null) ? empty() : of(value);
    }

    public T get() {
        return optional.get();
    }

    public boolean isPresent() {
        return optional.isPresent();
    }

    public void ifPresent(final Consumer<? super T> consumer) {
        optional.ifPresent(consumer);
    }

    public CustomOptional<T> filter(final Predicate<? super T> predicate) {
        return new CustomOptional<>(optional.filter(predicate));
    }

    public <U> CustomOptional<U> map(final Function<? super T, ? extends U> mapper) {
        return new CustomOptional<>(optional.map(mapper));
    }

    public <U> CustomOptional<U> flatMap(final Function<? super T, ? extends CustomOptional<U>> mapper) {
        return new CustomOptional<>(optional.flatMap(mapper.andThen(cu -> cu.getOptional())));
    }

    public T orElse(final T other) {
        return optional.orElse(other);
    }

    public T orElseGet(final Supplier<? extends T> other) {
        return optional.orElseGet(other);
    }

    public <X extends Throwable> T orElseThrow(final Supplier<? extends X> exceptionSuppier) throws X {
        return optional.orElseThrow(exceptionSuppier);
    }

    public Stream<T> flatStream() {
        if (!optional.isPresent()) {
            return Stream.empty();
        }
        return Stream.of(get());
    }

    public T getTOrNull() {
        if (!optional.isPresent()) {
            return null;
        }
        return get();
    }

    @Override
    public boolean equals(final Object obj) {
        return optional.equals(obj);
    }

    @Override
    public int hashCode() {
        return optional.hashCode();
    }

    @Override
    public String toString() {
        return optional.toString();
    }
}

你会看到我添加了flatStream(),如下所示:

public Stream<T> flatStream() {
    if (!optional.isPresent()) {
        return Stream.empty();
    }
    return Stream.of(get());
}

用作:

String result = Stream.of("a", "b", "c", "de", "fg", "hij")
        .map(this::resolve)
        .flatMap(CustomOptional::flatStream)
        .findFirst()
        .get();

你仍然需要返回一个流<T>在这里,因为你不能返回T,因为if !optional.isPresent(),然后T == null如果你声明这样,但然后你的. flatmap (CustomOptional::flatStream)将尝试添加null到流,这是不可能的。

为例:

public T getTOrNull() {
    if (!optional.isPresent()) {
        return null;
    }
    return get();
}

用作:

String result = Stream.of("a", "b", "c", "de", "fg", "hij")
        .map(this::resolve)
        .map(CustomOptional::getTOrNull)
        .findFirst()
        .get();

现在将在流操作中抛出NullPointerException。

结论

你用的方法,实际上是最好的方法。

其他回答

使用reduce的略短版本:

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

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

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

如果你不介意使用第三方库,你可以使用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的创造者。

Java 9

可选的。流已添加到JDK 9。这使你能够做以下事情,而不需要任何帮助方法:

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

Java 8

是的,这是API中的一个小洞,因为将可选的<T>转换为0或1长度的流<T>有点不方便。你可以这样做:

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

不过,在flatMap中使用三元操作符有点麻烦,所以最好写一个小的帮助函数来做这件事:

/**
 * 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) {
    if (opt.isPresent())
        return Stream.of(opt.get());
    else
        return Stream.empty();
}

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

在这里,我内联了对resolve()的调用,而不是使用单独的map()操作,但这只是个人喜好的问题。

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

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

很可能你做错了。

Java 8 Optional不是这样使用的。它通常只保留给终端流操作,这些操作可能返回值,也可能不返回值,例如find。

在您的情况下,最好先尝试找到一种廉价的方法来过滤那些可解析的项,然后将第一个项作为可选项,并将其作为最后一个操作进行解析。更好的方法是,找到第一个可解决的项目并解决它,而不是过滤。

things.filter(Thing::isResolvable)
      .findFirst()
      .flatMap(this::resolve)
      .get();

经验法则是,在将流中的项转换为其他内容之前,应该努力减少它们的数量。当然是YMMV。