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

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

有人有更好的主意吗?


当前回答

很可能你做错了。

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

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

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

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

其他回答

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

你声称你不想要.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。

结论

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

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。

使用reduce的略短版本:

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

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

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

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

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

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()操作,但这只是个人喜好的问题。