使用Java 8的可选流程::flatMap。

时间:2022-06-17 20:43:49

The new Java 8 stream framework and friends make for some very concise java code, but I have come across a seemingly-simple situation that is tricky to do concisely.

新的Java 8流框架和朋友为一些非常简洁的Java代码做了准备,但我遇到了一种看似简单的情况,但要做到简明扼要是很困难的。

Consider a List<Thing> things and method Optional<Other> resolve(Thing thing). I want to map the Things to Optional<Other>s and get the first Other. The obvious solution would be to use things.stream().flatMap(this::resolve).findFirst(), but flatMap requires that you return a stream, and Optional doesn't have a stream() method (or is it a Collection or provide a method to convert it to or view it as a Collection).

考虑一个列表< >的东西和方法可选的 <其他> 解析(东西)。我想把这些东西映射到可选的 <其他> s,然后得到第一个。显然的解决方案是使用things.stream().flatMap(这是:resolve). findfirst(),但是flatMap要求您返回一个流,而可选的没有流()方法(或者它是一个集合,或者提供一个方法来将它转换成一个集合)。

The best I can come up with is this:

我能想到的最好的方法是:

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

But that seems awfully long-winded for what seems like a very common case. Anyone have a better idea?

但这似乎是一种非常普遍的情况。有人有更好的主意吗?

9 个解决方案

#1


177  

Java 9

Optional.stream has been added to JDK 9. This enables you to do the following, without the need of any helper method:

可选的。已将流添加到JDK 9。这使您能够执行以下操作,而不需要任何辅助方法:

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

Java 8

Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional into a zero-or-one length Stream. You could do this:

是的,这是API中的一个小洞,在这个小洞里,把一个可选项变成一个零或一个长度的流是很不方便的。你可以这样做:

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

Having the ternary operator inside the flatMap is a bit cumbersome, though, so it might be better to write a little helper function to do this:

不过,在平面图中使用三元操作符有点麻烦,所以最好编写一个小的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) {
    if (opt.isPresent())
        return Stream.of(opt.get());
    else
        return Stream.empty();
}

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

Here, I've inlined the call to resolve() instead of having a separate map() operation, but this is a matter of taste.

在这里,我已经在调用中添加了resolve(),而不是使用单独的map()操作,但是这是一个味道的问题。

#2


53  

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 * rejected edit history, so I decided to surface it as a separate answer myself.

我将根据用户srborlongan对我的另一个答案进行的修改来添加第二个答案。我认为这项技术很有趣,但它并不适合作为我的答案的编辑。其他人同意了,提议的编辑被否决了。(我不是选民。)不过,这项技术有其优点。如果srborlongan发表自己的答案,那将是最好的。这还没有发生,我不希望在*的迷雾中丢失的技术被拒绝编辑历史,所以我决定把它作为一个单独的答案。

Basically the technique is to use some of the Optional methods in a clever way to avoid having to use a ternary operator (? :) or an if/else statement.

基本的方法是使用一些可选的方法来避免使用三元运算符(?或if/else语句。

My inline example would be rewritten this way:

我的内联示例将以这种方式重写:

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

An my example that uses a helper method would be rewritten this way:

使用辅助方法的示例将以这种方式重写:

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

COMMENTARY

评论

Let's compare the original vs modified versions directly:

让我们直接比较原始vs修改版本:

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

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

The original is a straightforward if workmanlike approach: we get an Optional<Other>; if it has a value, we return a stream containing that value, and if it has no value, we return an empty stream. Pretty simple and easy to explain.

如果采用workmanlike的方法,那么原始的方法是简单的:我们得到一个可选的 <其他> ;如果它有一个值,我们返回一个包含该值的流,如果它没有值,我们返回一个空流。很简单,也很容易解释。

The modification is clever and has the advantage that it avoids conditionals. (I know that some people dislike the ternary operator. If misused it can indeed make code hard to understand.) However, sometimes things can be too clever. The modified code also starts off with an Optional<Other>. Then it calls Optional.map which is defined as follows:

修改是聪明的,并且具有避免条件的优点。我知道有些人不喜欢三元运算符。如果误用它确实会使代码难以理解。然而,有时事情会变得过于聪明。修改后的代码也以可选的 <其他> 开始。然后它调用可选的。地图定义如下:

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

如果存在一个值,将提供的映射函数应用到它,如果结果为非空,返回一个可选的描述结果。否则返回一个空的可选。

The map(Stream::of) call returns an Optional<Stream<Other>>. If a value was present in the input Optional, the returned Optional contains a Stream that contains the single Other result. But if the value was not present, the result is an empty Optional.

该映射(流::of)调用返回可选的 >。如果一个值存在于输入选项中,返回的可选项包含一个包含另一个结果的流。但是如果值不存在,则结果是空的可选。

Next, the call to orElseGet(Stream::empty) returns a value of type Stream<Other>. If its input value is present, it gets the value, which is the single-element Stream<Other>. Otherwise (if the input value is absent) it returns an empty Stream<Other>. So the result is correct, the same as the original conditional code.

接下来,调用orElseGet(流::empty)返回类型流的值 <其他> 。如果它的输入值存在,它就会得到值,即单元素流 <其他> 。否则(如果没有输入值),则返回空流 <其他> 。所以结果是正确的,和原始的条件代码一样。

In the comments discussing on my answer, regarding the rejected edit, I had described this technique as "more concise but also more obscure". I stand by this. It took me a while to figure out what it was doing, and it also took me a while to write up the above description of what it was doing. The key subtlety is the transformation from Optional<Other> to Optional<Stream<Other>>. Once you grok this it makes sense, but it wasn't obvious to me.

在讨论我的回答的评论中,关于被拒绝的编辑,我把这个技巧描述为“更简洁,但也更隐晦”。我坚持这一点。我花了一段时间才弄明白它在做什么,我也花了一段时间来描述它所做的事情。关键的微妙之处是,从可选的 <其他> 到可选的 >。一旦你知道了这是有道理的,但对我来说并不明显。

I'll acknowledge, though, that things that are initially obscure can become idiomatic over time. It might be that this technique ends up being the best way in practice, at least until Optional.stream gets added (if it ever does).

不过,我承认,一开始有些模糊的东西会随着时间的推移成为习惯。这可能是实践中最好的方法,至少在可选的时候。流被添加(如果有的话)。

UPDATE: Optional.stream has been added to JDK 9.

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

#3


11  

You cannot do it more concise as you are already doing.

你不能像你已经做的那样做得更简洁。

You claim that you do not want .filter(Optional::isPresent) and .map(Optional::get).

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

This has been resolved by the method @StuartMarks describes, however as a result you now map it to an Optional<T>, so now you need to use .flatMap(this::streamopt) and a get() in the end.

这个方法已经通过@StuartMarks的方法解决了,但是现在您可以将它映射到一个可选的 ,所以现在您需要使用. flatmap(这个::streamopt)和一个get()。

So it still consists of two statements and you can now get exceptions with the new method! Because, what if every optional is empty? Then the findFirst() will return an empty optional and your get() will fail!

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

So what you have:

所以你有:

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

is actually the best way to accomplish what you want, and that is you want to save the result as a T, not as an Optional<T>.

实际上是实现你想要的最好的方法,这就是你想把结果保存为T,而不是可选的

I took the liberty of creating a CustomOptional<T> class that wraps the Optional<T> and provides an extra method, flatStream(). Note that you cannot extend Optional<T>:

我冒冒地创建了一个自定义的 类,它封装了可选的 ,并提供了一个额外的方法,flatStream()。注意,您不能扩展可选的 :

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

You will see that I added flatStream(), as here:

您将看到我添加了flatStream(),如下所示:

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

Used as:

用作:

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

You still will need to return a Stream<T> here, as you cannot return T, because if !optional.isPresent(), then T == null if you declare it such, but then your .flatMap(CustomOptional::flatStream) would attempt to add null to a stream and that is not possible.

您仍然需要返回一个流 ,因为您不能返回T,因为如果! option. ispresent(),那么如果您声明了它,那么T == null,那么您的. flatmap (CustomOptional::flatStream)将尝试向流中添加null,这是不可能的。

As example:

为例:

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

Used as:

用作:

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

Will now throw a NullPointerException inside the stream operations.

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

Conclusion

The method you used, is actually the best method.

您使用的方法实际上是最好的方法。

#4


5  

A slightly shorter version using reduce:

一个稍短的版本使用减少:

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

You could also move the reduce function to a static utility method and then it becomes:

你也可以将reduce函数移动到静态效用方法,然后它变成:

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

#5


3  

As my previous answer appeared not to be very popular, I will give this another go.

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

A short answer:

You are mostly on a right track. The shortest code to get to your desired output I could come up with is this:

你基本上是在正确的轨道上。要达到你想要的输出的最短代码,我能想到的是:

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

This will fit all your requirements:

这将符合您的所有要求:

  1. It will find first response that resolves to a nonempty Optional<Result>
  2. 它将找到第一个响应,它解析为一个非空的可选的
  3. It calls this::resolve lazily as needed
  4. 它称之为::根据需要延迟解决。
  5. this::resolve will not be called after first non-empty result
  6. 这个::在第一个非空结果之后,不调用解析。
  7. It will return Optional<Result>
  8. 它将返回可选 <结果>

Longer answer

The only modification compared to OP initial version was that I removed .map(Optional::get) before call to .findFirst() and added .flatMap(o -> o) as the last call in the chain.

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

This has a nice effect of getting rid of the double-Optional, whenever stream finds an actual result.

这有一个很好的效果,可以去掉double-Optional,只要流找到一个实际的结果。

You can't really go any shorter than this in Java.

在Java中不能比这个更短。

The alternative snippet of code using the more conventional for loop technique is going to be about same number of lines of code and have more or less same order and number of operations you need to perform:

使用更常规的for循环技术的代码片段将会是相同数量的代码行,并且具有或多或少相同的顺序和您需要执行的操作数:

  1. Calling this.resolve,
  2. 调用this.resolve,
  3. filtering based on Optional.isPresent
  4. 基于Optional.isPresent过滤
  5. returning the result and
  6. 返回结果
  7. some way of dealing with negative result (when nothing was found)
  8. 一些处理消极结果的方法(当没有发现的时候)

Just to prove that my solution works as advertised, I wrote a small test program:

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

public class * {

    public static void main( String... args ) {
        try {
            final int integer = Stream.of( args )
                    .peek( s -> System.out.println( "Looking at " + s ) )
                    .map( *::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();
        }
    }

}

(It does have few extra lines for debugging and verifying that only as many calls to resolve as needed...)

(它确实没有额外的代码来进行调试和验证,只有在需要的时候,才会有许多需要解决的调用…)

Executing this on a command line, I got the following results:

在命令行中执行此操作,我得到了以下结果:

$ 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

#6


2  

If you don't mind to use a third party library you may use Javaslang. It is like Scala, but implemented in Java.

如果您不介意使用第三方库,您可以使用Javaslang。它就像Scala,但在Java中实现。

It comes with a complete immutable collection library that is very similar to that known from Scala. These collections replace Java's collections and Java 8's Stream. It also has its own implementation of Option.

它提供了一个完全不可变的集合库,这与Scala中所知道的非常相似。这些集合替换了Java的集合和Java 8的流。它也有自己的选项的实现。

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

Here is a solution for the example of the initial question:

下面是一个关于最初问题的例子:

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 + ")"; }
}

Disclaimer: I'm the creator of Javaslang.

免责声明:我是Javaslang的创始人。

#7


2  

Null is supported by the Stream provided My library AbacusUtil. Here is code:

该流支持Null,提供了我的库AbacusUtil。下面是代码:

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

#8


1  

Late to the party, but what about

聚会迟到了,但又怎么样呢?

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

things.stream(). map(::解决).filter(可选::isPresent).findFirst(). get();

You can get rid of the last get() if you create a util method to convert optional to stream manually:

如果您创建了一个util方法来手动转换可选的流,那么您可以删除最后一个get():

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

things.stream(). map(::解决).flatMap(Util::optionalToStream).findFirst();

If you return stream right away from your resolve function, you save one more line.

如果您从解析函数返回流,则可以再保存一行。

#9


-4  

Most likely You are doing it wrong.

很可能你做错了。

Java 8 Optional is not meant to be used in this manner. It is usually only reserved for terminal stream operations that may or may not return a value, like find for example.

Java 8可选并不是要以这种方式使用。它通常只保留于可能返回值或可能不返回值的终端流操作,例如find。

In your case it might be better to first try to find a cheap way to filter out those items that are resolvable and then get the first item as an optional and resolve it as a last operation. Better yet - instead of filtering, find the first resolvable item and resolve it.

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

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

Rule of thumb is that you should strive to reduce number of items in the stream before you transform them to something else. YMMV of course.

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

#1


177  

Java 9

Optional.stream has been added to JDK 9. This enables you to do the following, without the need of any helper method:

可选的。已将流添加到JDK 9。这使您能够执行以下操作,而不需要任何辅助方法:

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

Java 8

Yes, this was a small hole in the API, in that it's somewhat inconvenient to turn an Optional into a zero-or-one length Stream. You could do this:

是的,这是API中的一个小洞,在这个小洞里,把一个可选项变成一个零或一个长度的流是很不方便的。你可以这样做:

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

Having the ternary operator inside the flatMap is a bit cumbersome, though, so it might be better to write a little helper function to do this:

不过,在平面图中使用三元操作符有点麻烦,所以最好编写一个小的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) {
    if (opt.isPresent())
        return Stream.of(opt.get());
    else
        return Stream.empty();
}

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

Here, I've inlined the call to resolve() instead of having a separate map() operation, but this is a matter of taste.

在这里,我已经在调用中添加了resolve(),而不是使用单独的map()操作,但是这是一个味道的问题。

#2


53  

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 * rejected edit history, so I decided to surface it as a separate answer myself.

我将根据用户srborlongan对我的另一个答案进行的修改来添加第二个答案。我认为这项技术很有趣,但它并不适合作为我的答案的编辑。其他人同意了,提议的编辑被否决了。(我不是选民。)不过,这项技术有其优点。如果srborlongan发表自己的答案,那将是最好的。这还没有发生,我不希望在*的迷雾中丢失的技术被拒绝编辑历史,所以我决定把它作为一个单独的答案。

Basically the technique is to use some of the Optional methods in a clever way to avoid having to use a ternary operator (? :) or an if/else statement.

基本的方法是使用一些可选的方法来避免使用三元运算符(?或if/else语句。

My inline example would be rewritten this way:

我的内联示例将以这种方式重写:

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

An my example that uses a helper method would be rewritten this way:

使用辅助方法的示例将以这种方式重写:

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

COMMENTARY

评论

Let's compare the original vs modified versions directly:

让我们直接比较原始vs修改版本:

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

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

The original is a straightforward if workmanlike approach: we get an Optional<Other>; if it has a value, we return a stream containing that value, and if it has no value, we return an empty stream. Pretty simple and easy to explain.

如果采用workmanlike的方法,那么原始的方法是简单的:我们得到一个可选的 <其他> ;如果它有一个值,我们返回一个包含该值的流,如果它没有值,我们返回一个空流。很简单,也很容易解释。

The modification is clever and has the advantage that it avoids conditionals. (I know that some people dislike the ternary operator. If misused it can indeed make code hard to understand.) However, sometimes things can be too clever. The modified code also starts off with an Optional<Other>. Then it calls Optional.map which is defined as follows:

修改是聪明的,并且具有避免条件的优点。我知道有些人不喜欢三元运算符。如果误用它确实会使代码难以理解。然而,有时事情会变得过于聪明。修改后的代码也以可选的 <其他> 开始。然后它调用可选的。地图定义如下:

If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result. Otherwise return an empty Optional.

如果存在一个值,将提供的映射函数应用到它,如果结果为非空,返回一个可选的描述结果。否则返回一个空的可选。

The map(Stream::of) call returns an Optional<Stream<Other>>. If a value was present in the input Optional, the returned Optional contains a Stream that contains the single Other result. But if the value was not present, the result is an empty Optional.

该映射(流::of)调用返回可选的 >。如果一个值存在于输入选项中,返回的可选项包含一个包含另一个结果的流。但是如果值不存在,则结果是空的可选。

Next, the call to orElseGet(Stream::empty) returns a value of type Stream<Other>. If its input value is present, it gets the value, which is the single-element Stream<Other>. Otherwise (if the input value is absent) it returns an empty Stream<Other>. So the result is correct, the same as the original conditional code.

接下来,调用orElseGet(流::empty)返回类型流的值 <其他> 。如果它的输入值存在,它就会得到值,即单元素流 <其他> 。否则(如果没有输入值),则返回空流 <其他> 。所以结果是正确的,和原始的条件代码一样。

In the comments discussing on my answer, regarding the rejected edit, I had described this technique as "more concise but also more obscure". I stand by this. It took me a while to figure out what it was doing, and it also took me a while to write up the above description of what it was doing. The key subtlety is the transformation from Optional<Other> to Optional<Stream<Other>>. Once you grok this it makes sense, but it wasn't obvious to me.

在讨论我的回答的评论中,关于被拒绝的编辑,我把这个技巧描述为“更简洁,但也更隐晦”。我坚持这一点。我花了一段时间才弄明白它在做什么,我也花了一段时间来描述它所做的事情。关键的微妙之处是,从可选的 <其他> 到可选的 >。一旦你知道了这是有道理的,但对我来说并不明显。

I'll acknowledge, though, that things that are initially obscure can become idiomatic over time. It might be that this technique ends up being the best way in practice, at least until Optional.stream gets added (if it ever does).

不过,我承认,一开始有些模糊的东西会随着时间的推移成为习惯。这可能是实践中最好的方法,至少在可选的时候。流被添加(如果有的话)。

UPDATE: Optional.stream has been added to JDK 9.

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

#3


11  

You cannot do it more concise as you are already doing.

你不能像你已经做的那样做得更简洁。

You claim that you do not want .filter(Optional::isPresent) and .map(Optional::get).

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

This has been resolved by the method @StuartMarks describes, however as a result you now map it to an Optional<T>, so now you need to use .flatMap(this::streamopt) and a get() in the end.

这个方法已经通过@StuartMarks的方法解决了,但是现在您可以将它映射到一个可选的 ,所以现在您需要使用. flatmap(这个::streamopt)和一个get()。

So it still consists of two statements and you can now get exceptions with the new method! Because, what if every optional is empty? Then the findFirst() will return an empty optional and your get() will fail!

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

So what you have:

所以你有:

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

is actually the best way to accomplish what you want, and that is you want to save the result as a T, not as an Optional<T>.

实际上是实现你想要的最好的方法,这就是你想把结果保存为T,而不是可选的

I took the liberty of creating a CustomOptional<T> class that wraps the Optional<T> and provides an extra method, flatStream(). Note that you cannot extend Optional<T>:

我冒冒地创建了一个自定义的 类,它封装了可选的 ,并提供了一个额外的方法,flatStream()。注意,您不能扩展可选的 :

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

You will see that I added flatStream(), as here:

您将看到我添加了flatStream(),如下所示:

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

Used as:

用作:

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

You still will need to return a Stream<T> here, as you cannot return T, because if !optional.isPresent(), then T == null if you declare it such, but then your .flatMap(CustomOptional::flatStream) would attempt to add null to a stream and that is not possible.

您仍然需要返回一个流 ,因为您不能返回T,因为如果! option. ispresent(),那么如果您声明了它,那么T == null,那么您的. flatmap (CustomOptional::flatStream)将尝试向流中添加null,这是不可能的。

As example:

为例:

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

Used as:

用作:

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

Will now throw a NullPointerException inside the stream operations.

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

Conclusion

The method you used, is actually the best method.

您使用的方法实际上是最好的方法。

#4


5  

A slightly shorter version using reduce:

一个稍短的版本使用减少:

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

You could also move the reduce function to a static utility method and then it becomes:

你也可以将reduce函数移动到静态效用方法,然后它变成:

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

#5


3  

As my previous answer appeared not to be very popular, I will give this another go.

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

A short answer:

You are mostly on a right track. The shortest code to get to your desired output I could come up with is this:

你基本上是在正确的轨道上。要达到你想要的输出的最短代码,我能想到的是:

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

This will fit all your requirements:

这将符合您的所有要求:

  1. It will find first response that resolves to a nonempty Optional<Result>
  2. 它将找到第一个响应,它解析为一个非空的可选的
  3. It calls this::resolve lazily as needed
  4. 它称之为::根据需要延迟解决。
  5. this::resolve will not be called after first non-empty result
  6. 这个::在第一个非空结果之后,不调用解析。
  7. It will return Optional<Result>
  8. 它将返回可选 <结果>

Longer answer

The only modification compared to OP initial version was that I removed .map(Optional::get) before call to .findFirst() and added .flatMap(o -> o) as the last call in the chain.

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

This has a nice effect of getting rid of the double-Optional, whenever stream finds an actual result.

这有一个很好的效果,可以去掉double-Optional,只要流找到一个实际的结果。

You can't really go any shorter than this in Java.

在Java中不能比这个更短。

The alternative snippet of code using the more conventional for loop technique is going to be about same number of lines of code and have more or less same order and number of operations you need to perform:

使用更常规的for循环技术的代码片段将会是相同数量的代码行,并且具有或多或少相同的顺序和您需要执行的操作数:

  1. Calling this.resolve,
  2. 调用this.resolve,
  3. filtering based on Optional.isPresent
  4. 基于Optional.isPresent过滤
  5. returning the result and
  6. 返回结果
  7. some way of dealing with negative result (when nothing was found)
  8. 一些处理消极结果的方法(当没有发现的时候)

Just to prove that my solution works as advertised, I wrote a small test program:

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

public class * {

    public static void main( String... args ) {
        try {
            final int integer = Stream.of( args )
                    .peek( s -> System.out.println( "Looking at " + s ) )
                    .map( *::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();
        }
    }

}

(It does have few extra lines for debugging and verifying that only as many calls to resolve as needed...)

(它确实没有额外的代码来进行调试和验证,只有在需要的时候,才会有许多需要解决的调用…)

Executing this on a command line, I got the following results:

在命令行中执行此操作,我得到了以下结果:

$ 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

#6


2  

If you don't mind to use a third party library you may use Javaslang. It is like Scala, but implemented in Java.

如果您不介意使用第三方库,您可以使用Javaslang。它就像Scala,但在Java中实现。

It comes with a complete immutable collection library that is very similar to that known from Scala. These collections replace Java's collections and Java 8's Stream. It also has its own implementation of Option.

它提供了一个完全不可变的集合库,这与Scala中所知道的非常相似。这些集合替换了Java的集合和Java 8的流。它也有自己的选项的实现。

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

Here is a solution for the example of the initial question:

下面是一个关于最初问题的例子:

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 + ")"; }
}

Disclaimer: I'm the creator of Javaslang.

免责声明:我是Javaslang的创始人。

#7


2  

Null is supported by the Stream provided My library AbacusUtil. Here is code:

该流支持Null,提供了我的库AbacusUtil。下面是代码:

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

#8


1  

Late to the party, but what about

聚会迟到了,但又怎么样呢?

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

things.stream(). map(::解决).filter(可选::isPresent).findFirst(). get();

You can get rid of the last get() if you create a util method to convert optional to stream manually:

如果您创建了一个util方法来手动转换可选的流,那么您可以删除最后一个get():

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

things.stream(). map(::解决).flatMap(Util::optionalToStream).findFirst();

If you return stream right away from your resolve function, you save one more line.

如果您从解析函数返回流,则可以再保存一行。

#9


-4  

Most likely You are doing it wrong.

很可能你做错了。

Java 8 Optional is not meant to be used in this manner. It is usually only reserved for terminal stream operations that may or may not return a value, like find for example.

Java 8可选并不是要以这种方式使用。它通常只保留于可能返回值或可能不返回值的终端流操作,例如find。

In your case it might be better to first try to find a cheap way to filter out those items that are resolvable and then get the first item as an optional and resolve it as a last operation. Better yet - instead of filtering, find the first resolvable item and resolve it.

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

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

Rule of thumb is that you should strive to reduce number of items in the stream before you transform them to something else. YMMV of course.

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