281

新しい Java 8 ストリーム フレームワークとその仲間は、いくつかの非常に簡潔な Java コードを作成しますが、私は簡潔に行うのが難しい、一見単純な状況に出くわしました。

List<Thing> thingsand メソッドを考えてみましょうOptional<Other> resolve(Thing thing)Things をOptional<Other>s にマップして、最初の を取得したいOther。明らかな解決策は を使用することですが、ストリームを返す必要がありthings.stream().flatMap(this::resolve).findFirst()、メソッドを持っていません(または、に変換するか、 として表示するメソッドを提供します)。flatMapOptionalstream()CollectionCollection

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

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

しかし、それは非常に一般的なケースのように見えるものに対して、非常に長々としたようです. 誰でも良いアイデアがありますか?

4

12 に答える 12

316

Java 9

Optional.streamがJDK 9に追加されました。これにより、ヘルパー・メソッドを必要とせずに次のことが可能になります:

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

Java 8

はい、これは API の小さな穴でした。 anOptional<T>を 0 または 1 の length に変換するのはやや不便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()が、これは好みの問題です。

于 2014-03-29T04:10:05.437 に答える
71

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

基本的には、Optionalメソッドのいくつかを巧妙な方法で使用して、三項演算子 ( ? :) や 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>です。値がある場合はその値を含むストリームを返し、値がない場合は空のストリームを返します。かなりシンプルで簡単に説明できます。

この変更は巧妙で、条件を回避できるという利点があります。(三項演算子を嫌う人がいることは知っています。使い方を誤ると、コードが理解しにくくなる可能性があります。)しかし、物事が巧妙すぎる場合もあります。変更されたコードもOptional<Other>. Optional.map次に、次のように定義されたものを呼び出します。

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

map(Stream::of)呼び出しは を返しますOptional<Stream<Other>>。入力 Optional に値が存在する場合、返された Optional には、単一の Other 結果を含む Stream が含まれます。ただし、値が存在しない場合、結果は空の Optional になります。

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

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

ただし、最初はあいまいなことが時間の経過とともに慣用的になる可能性があることは認めます。少なくとも追加されるまでOptional.streamは、この手法が実際には最善の方法である可能性があります (追加される場合)。

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

于 2014-04-02T20:41:16.080 に答える
19

すでに行っているように、これ以上簡潔にすることはできません。

あなたは望んでいないと主張し.filter(Optional::isPresent) .map(Optional::get) .

これは @StuartMarks が説明するメソッドによって解決されましたが、結果としてそれを にマップするようになったため、最後にandOptional<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();

を返すことはできないため、ここでaを返す必要があります。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ストリーム操作の中でスローするようになりました。

結論

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

于 2014-03-29T18:35:56.580 に答える
6

を使用したわずかに短いバージョンreduce:

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

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

  .reduce(Optional.empty(), Util::firstPresent );
于 2014-11-30T17:04:02.530 に答える
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
于 2016-03-31T13:26:53.520 に答える
-5

ほとんどの場合、あなたは間違っています。

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

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

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

経験則として、ストリーム内のアイテムを別のものに変換する前に、その数を減らすように努力する必要があります。もちろんYMMV。

于 2014-03-29T01:15:20.983 に答える