Stream :: flatMapでのJava 8のオプションの使用


240

新しいJava 8ストリームフレームワークとその仲間は、非常に簡潔なJavaコードを作成しますが、簡潔に行うのが難しい一見シンプルな状況に遭遇しました。

List<Thing> thingsとメソッドを検討してくださいOptional<Other> resolve(Thing thing)ThingsをOptional<Other>s にマップして、最初のを取得しOtherます。明白な解決策はを使用することですが、ストリームを返す必要things.stream().flatMap(this::resolve).findFirst()flatMapありOptionalstream()メソッドがない(またはCollectionそれをに変換するか、それをとして表示するメソッドを提供するCollection

私が思いつくことができる最高のものはこれです:

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

しかし、それは非常に一般的なケースのように思えるので、ひどく長い時間のように思えます。誰かがより良いアイデアを持っていますか?


あなたの例で少しコーディングした後、私は実際に明示的なバージョンが存在する場合、それよりも読みやすいバージョンを見つけ.flatMap(Optional::toStream)ました。
skiwi 2014年

19
@skiwiええと、Optional.streamJDK 9には現在存在しています...
スチュアートマーク

これがどこに文書化されているのか、そしてそれを取得するためのプロセスはどのようなものであったのか、私は興味があります。他にも実際に存在する必要があるように見えるメソッドがいくつかあります。APIの変更に関する議論がどこで行われているのか、私は興味があります。
Yona Appletree、2015年


10
おかしいのは、JDK-8050820が実際にこの質問を説明で参照していることです。
Didier L

回答:


265

Java 9

Optional.stream JDK 9に追加されました。これにより、ヘルパーメソッドを必要とせずに以下を実行できます。

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

Java 8

はい、これはAPIの小さな穴でした。Optional<T>長さを0または1に変換するのはやや不便Stream<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()操作ではなくへの呼び出しをインライン化しましたが、これは好みの問題です。


2
Java 9まではAPIを変更できないと思います。
assylias 14年

5
@Hypherありがとう。.filter()。map()テクニックはそれほど悪くなく、ヘルパーメソッドへの依存を回避します。『もっと簡潔な方法があればいいのに。Optional.stream()を追加する方法を調べます。
スチュアートマーク、2014

43
私の好み:static <T> Stream<T> streamopt(Optional<T> opt) { return opt.map(Stream::of).orElse(Stream.empty()); }
kubek2k

5
私は彼らがOptional過負荷を追加することを望みStream#flatMapます...そのようにあなたが書くことができるようにstream().flatMap(this::resolve)
フレーク

4
@flkesええ、私たちはこのアイデアを追い求めてきましたが、(JDK 9には)があるので、それほど多くの価値を追加しているようには見えませんOptional.stream()
Stuart Marks

69

ユーザーsrborlonganによる編集案に基づくこの2番目の回答を、他の回答に追加ます。提案された手法は興味深いと思いますが、私の回答の編集にはあまり適していませんでした。他の人々は同意し、提案された編集は否決されました。(私は有権者の1人ではありませんでした。)ただし、この手法にはメリットがあります。srborlonganが自分の答えを投稿していたとしたら、それは最高でした。これはまだ起こっていないので、StackOverflowで拒否された編集履歴の霧の中でこの手法が失われたくなかったので、私はそれを別の答えとして表面化することにしました。

基本的にはOptional、3項演算子(? :)またはif / elseステートメントを使用する必要がないように、いくつかのメソッドを巧妙な方法で使用します。

私のインラインの例はこのように書き直されます:

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

ヘルパーメソッドを使用する私の例は、次のように書き直されます。

/**
 * 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>です。値がある場合はその値を含むストリームを返し、値がない場合は空のストリームを返します。とてもシンプルで簡単に説明できます。

変更は賢く、条件を回避するという利点があります。(3項演算子が嫌いな人もいることは知っています。誤用すると、コードが理解しにくくなることがあります。)ただし、場合によっては、賢すぎる場合もあります。変更されたコードも。で始まりOptional<Other>ます。次にOptional.map、次のように定義されている呼び出します。

値が存在する場合は、提供されたマッピング関数をその値に適用し、結果がnull以外の場合は、結果を説明するオプションを返します。それ以外の場合は、空のオプションを返します。

map(Stream::of)呼び出しが返されますOptional<Stream<Other>>。入力オプションに値が存在する場合、返されるオプションには、単一のその他の結果を含むストリームが含まれます。ただし、値が存在しない場合、結果は空のオプションになります。

次に、orElseGet(Stream::empty)型の値を返す呼び出しStream<Other>。入力値が存在する場合は、単一要素である値を取得しますStream<Other>。それ以外の場合(入力値がない場合)は空を返しますStream<Other>。したがって、結果は元の条件付きコードと同じで正しいです。

拒否された編集に関して、私の回答についてのコメントで、私はこの手法を「より簡潔であるが、あいまいである」と説明しました。私はこれを支持します。それが何をしているのかを理解するのにしばらく時間がかかりました。また、何をしているのかの上記の説明を書くのにも少し時間がかかりました。重要な微妙な点は、Optional<Other>Optional<Stream<Other>>です。ひとたびこれを理解すると理にかなっていますが、それは私には明白ではありませんでした。

ただし、最初はあいまいなものは、時間の経過とともに慣用的になる可能性があることを認めます。このテクニックは、少なくとも実際には、実際には最良の方法になるかもしれませんOptional.stream追加さは(追加さ。

更新: Optional.stream JDK 9に追加されました。


16

すでにやっているので、もっと簡潔にすることはできません。

あなたがしたくない.filter(Optional::isPresent) 主張し.map(Optional::get)

これは、@ StuartMarksが記述するメソッドによって解決されましたが、結果として、これをにマップするOptional<T>ため、最後に.flatMap(this::streamopt)とを使用する必要がありますget()

したがって、これはまだ2つのステートメントで構成されており、新しいメソッドで例外を取得できます!なぜなら、すべてのオプションが空の場合はどうなりますか?次に、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();

あなたはまだ返す必要がありますStream<T>あなたが返すことができないとして、ここではT理由ならば、!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ストリーム操作の内部をスローします。

結論

あなたが使用した方法は、実際には最良の方法です。


6

使用する少し短いバージョンreduce

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

また、reduce関数を静的ユーティリティメソッドに移動すると、次のようになります。

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

6
私はこれが好きですが、これはストリーム内のすべてのアイテムを評価するので、findFirst()は現在のアイテムが見つかるまでしか評価しないことを指摘する価値があります。
ダンカンマクレガー2014

1
そして、残念ながら、各解決を実行することは、決断を下すことになります。しかし、それは賢いです。
Yona Appletree、2015

5

私の以前の答えはあまり人気がないように思われたので、これをもう一度やります。

短い答え:

あなたはほとんど正しい軌道に乗っています。私が思いつくことができるあなたの望ましい出力を得るための最短のコードはこれです:

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

これはすべての要件に適合します。

  1. 空でないことに解決する最初の応答を見つけます Optional<Result>
  2. this::resolve必要に応じて遅延呼び出し
  3. this::resolve 空でない最初の結果の後には呼び出されません
  4. 戻ります Optional<Result>

より長い答え

OPの初期バージョンと比較した唯一の変更点は、.map(Optional::get)呼び出し前に削除し、チェーンの最後の呼び出しとして.findFirst()追加.flatMap(o -> o)したことです。

これは、ストリームが実際の結果を見つけるたびに、double-Optionalを取り除くという素晴らしい効果があります。

Javaではこれより短くすることはできません。

より一般的なforループテクニックを使用したコードの代替スニペットは、ほぼ同じ数のコード行になり、実行する必要がある操作の数と順序はほぼ同じになります。

  1. 呼び出しthis.resolve
  2. に基づくフィルタリング Optional.isPresent
  3. 結果を返すと
  4. 否定的な結果を処理する何らかの方法(何も見つからなかった場合)

私のソリューションが宣伝どおりに機能することを証明するために、小さなテストプログラムを作成しました。

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

ローランド・テップと同じだと思います。1つのオプションの<optional <?>>でフラットにできるのに、なぜ誰かがstream <stream <?>>とフラットにするのでしょうか
Young Hyun Yoo

3

サードパーティのライブラリを使用しても構わない場合は、Javaslangを使用できます。Scalaに似ていますが、Javaで実装されています。

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

最初の質問の例の解決策は次のとおりです。

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の作成者です。


3

パーティーに遅れますが、どうですか

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

オプションを手動でストリームに変換するutilメソッドを作成すると、最後のget()を取り除くことができます。

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

resolve関数からすぐにストリームを返す場合は、もう1行保存します。


3

機能的なAPIのヘルパーを作成するためのファクトリメソッドを促進したいと思います。

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

推論:

  • 一般的にメソッド参照と同様に、ラムダ式と比較して、次のように、アクセス可能なスコープから変数を誤ってキャプチャすることはできません。

    t -> streamopt(resolve(o))

  • それは構成可能です。例えばFunction::andThen、ファクトリーメソッドの結果を呼び出すことができます:

    streamopt(this::resolve).andThen(...)

    一方、ラムダの場合は、最初にキャストする必要があります。

    ((Function<T, Stream<R>>) t -> streamopt(resolve(t))).andThen(...)


3

Nullは、MyライブラリAbacusUtilが提供するストリームでサポートされています。ここにコードがあります:

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

3

Java 8で立ち往生しているが、Guava 21.0以降にアクセスできる場合は、 Streams.streamしてオプションをストリームに変換ます。

したがって、

import com.google.common.collect.Streams;

あなたは書ける

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

0

そのことについて何?

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


ストリーミングして収集できるのに、なぜこれを行うのですか?
OneCricketeer

return list.stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()))、質問(およびリンクされた回答)と同じように...
OneCricketeer

私は間違っているかもしれませんが、isPresent()の使用を検討してからget()を使用することはお勧めしません。だから私はそれを避けようとします。
ラスタマン

.get() なし isPresent()で使用すると、IntelliJ
OneCricketeer

-5

おそらくあなたはそれを間違っています。

Java 8 Optionalは、この方法で使用するためのものではありません。これは通常、たとえばfindなど、値を返す場合と返さない場合があるターミナルストリーム操作のためにのみ予約されています。

あなたのケースでは、最初に解決可能なアイテムをフィルターする安価な方法を見つけてから、最初のアイテムをオプションとして取得し、それを最後の操作として解決することをお勧めします。さらに良い-フィルタリングの代わりに、最初の解決可能なアイテムを見つけて解決します。

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

経験則では、ストリーム内のアイテムを他のアイテムに変換する前に、アイテムの数を減らすよう努めるべきです。もちろんYMMV。


6
OPのresolve()メソッドがOptional <Other>を返すのは、Optionalの完全に賢明な使い方だと思います。もちろん、OPの問題領域について話すことはできませんが、何かが解決可能かどうかを判断する方法は、それを解決しようとすることかもしれません。その場合、Optionalは、「これは解決可能であった」というブール結果を解決の結果と融合し、成功した場合は、単一のAPI呼び出しに融合します。
スチュアートマークス

2
スチュアートは基本的に正しいです。望ましい順に検索語のセットがあり、何も返さない最初の検索語の結果を探しています。だから基本的にOptional<Result> searchFor(Term t)。それはオプションの意図に合うようです。また、stream()は遅延評価される必要があるため、最初に一致する用語を超えて用語を解決する余分な作業は発生しません。
Yona Appletree 2014年

質問は完全に賢明であり、OptionalでflatMapを使用することは、Scalaなどの他の同様のプログラミング言語でよく行われています。
dzs
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.