中断するか、Java 8ストリームから戻るか?


313

使用する場合は、外部反復を介してIterable我々の使用breakまたはreturnのために、各ループのように強化さから:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

どのように、breakまたは次のようなJava 8ラムダ式で内部反復returnを使用できますか?

someObjects.forEach(obj -> {
   //what to do here?
})

6
できません。実際のforステートメントを使用してください。
Boann 14


もちろんそれがあるために可能forEach()。解決策は良くありませんが、それ可能です。以下の私の答えを参照してください。
Honza Zidek 2015

別のアプローチを考えてみましょう。単にコードを実行したくないので、if内部の単純な条件でforEachうまくいきます。
Thomas Decaux

回答:


374

これが必要な場合は、を使用するべきではありませんがforEach、ストリームで使用可能な他のメソッドの1つです。どれがあなたの目標が何であるかに依存します。

たとえば、このループの目的が、いくつかの述語に一致する最初の要素を見つけることである場合:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

(注:ストリームは遅延評価されるため、これはコレクション全体を反復しません-条件に一致する最初のオブジェクトで停止します)。

条件が真である要素がコレクションにあるかどうかを知りたいだけの場合は、次のように使用できますanyMatch

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);

7
これは、オブジェクトを見つけることが目標である場合に機能しますが、その目標は通常returnfindSomethingメソッドからのステートメントによって提供されます。break通常、テイクウィル-操作の種類に関連付けられています
Marko Topolnik 14

1
@MarkoTopolnikはい、元の投稿者は、目標が正確に何であるかを知るのに十分な情報を私たちに提供していませんでした。「しばらくの間」は、私が述べた2つに加えて、3番目の可能性です。(ストリームで「しばらくの間」行う簡単な方法はありますか?)
Jesper

3
キャンセル動作を適切に実装することが目的の場合はどうですか?ユーザーがキャンセルを要求したことに気付いたときに、forEachラムダ内で実行時例外をスローするだけでできる最善のことは何ですか?
user2163960

1
@HonzaZidek編集されましたが、ポイントはそれが可能かどうかではなく、物事を行うための正しい方法とは何かです。forEachこのために強制的に使用しないでください。代わりに、より適切な別の方法を使用してください。
Jesper

1
@Jesper私はあなたに同意します、私は「例外ソリューション」が好きではなかったと書きました。ただし、「これは不可能です」という表現forEachは技術的に正しくありませんでした。私もあなたの解決策を好みますが、私の答えで提供された解決策が好ましいユースケースを想像できます:実際の例外のためにループを終了する必要があるとき。一般的に、フローを制御するために例外を使用しないでください。
Honza Zidek 2016年

54

これ可能ですIterable.forEach()(ただし、では確実ではありませんStream.forEach())。解決策は良くありませんが、それ可能です。

警告:ビジネスロジックの制御には使用しないでください。純粋にの実行中に発生する例外的な状況を処理するために使用してくださいforEach()。リソースへのアクセスが突然停止するなど、処理されたオブジェクトの1つがコントラクトに違反しています(たとえば、コントラクトでは、ストリーム内のすべての要素nullが突然ではなく予期せず1つである必要がありますnull)など。

のドキュメントによるとIterable.forEach()

すべての要素が処理されるか、アクションが例外をスローするIterable まで、の各要素に対して指定されたアクションを実行します...アクションによってスローされた例外は、呼び出し元にリレーされます。

したがって、すぐに内部ループを中断する例外をスローします。

コードはこのようなものになります- 私はそれ好きだとは言えませんが、うまくいきます。BreakException拡張する独自のクラスを作成しますRuntimeException

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

ことに注意してくださいtry...catchはないラムダ式の周りではなく、周りの全体のforEach()方法。より見やすくするには、次のコードの書き起こしを参照してください。

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

37
これは悪い習慣であり、問​​題の解決策として考えるべきではないと思います。初心者には誤解を招く恐れがあるので危険です。Effective Java 2nd Editionの第9章、項目57によると、「例外は例外的な状況でのみ使用してください」。さらに、「ランタイム例外を使用してプログラミングエラーを示します」。間違いなく、このソリューションを検討している人には@Jesperソリューションを検討することを強くお勧めします。
Louis F.

15
@LouisF。私は「好きだとは言えないがうまくいく」とはっきり言った。OPは「forEach()から抜け出す方法」を尋ねましたが、これが答えです。これをビジネスロジックの制御に使用しないでください。ただし、リソースへの接続がforEach()などの途中で突然利用できなくなるなど、例外を使用することは悪い習慣ではない、いくつかの便利な使用例を想像できます。明確にするために、回答に段落を追加しました。
Honza Zidek 2016年

1
これは素晴らしい解決策だと思います。Googleで「java例外」を検索し、さらに「ベストプラクティス」や「未チェック」などの単語を追加して検索したところ、例外の使用方法について論争があるようです。ストリームが数分かかるマップを実行していたため、コードでこのソリューションを使用しました。ユーザーがタスクをキャンセルできるようにしたいので、各計算の開始時にフラグ「isUserCancelRequested」をチェックし、trueの場合に例外をスローしました。クリーンで、例外コードはコードのごく一部に分離されており、機能します。
Jason

2
Stream.forEachは、呼び出し元にリレーされる例外について同じ強力な保証を提供しないことに注意してください。したがって、例外のスローは、でこのように機能することは保証されていませんStream.forEach
Radiodef

1
@Radiodefありがとうございます。元の投稿はについてIterable.forEach()でしたが、完全を期すために、私はあなたのポイントを私のテキストに追加しました。
Honza Zidek

43

ラムダでの戻りは、for-eachでの継続と同じですが、ブレークに相当するものはありません。あなたは続けるためにただ戻ることができます:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})

2
一般的な要件に対処するための素敵で慣用的なソリューション。受け入れられた回答は要件を推定します。
davidxxx 2018年

6
言い換えれば、これは実際の質問であった「Java 8ストリームのforEachからの中断または復帰」ではありません
JaroslavZáruba '24

1
ただし、これでもソースストリームを通じてレコードを「プル」します。これは、ある種のリモートデータセットをページングしている場合には悪いことです。
Adrian Baker

23

以下に、プロジェクトで使用したソリューションを示します。代わりにforEach単に使用してくださいallMatch

someObjects.allMatch(obj -> {
    return !some_condition_met;
});

11

どちらかあなたは(それが代わりに休憩を持つように)続けるためにかどうかを示す述語を使用する方法を使用する必要がある、あなたは、例外をスローする必要がある-もちろん、非常に醜いアプローチ、です。

したがって、次のforEachConditionalようなメソッドを記述できます。

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

ではなくPredicate<T>、同じ一般的なメソッド(a Tを取り、a を返すもの)を使用して独自の機能インターフェイスを定義することをお勧めしますboolが、期待をより明確に示す名前を付けPredicate<T>ます。これは理想的ではありません。


1
とにかくJava 8を使用している場合は、このヘルパーメソッドを作成するよりも、実際にStreams API と機能的なアプローチを使用する方が望ましいと思います。
skiwi 2014

3
これは古典的なtakeWhile操作であり、この質問は、Streams APIの不足がどの程度感じられるかを示すものの1つにすぎません。
Marko Topolnik 14

2
@Marko:takeWhileは、アイテムに対してアクションを実行するのではなく、アイテムを生成する操作のように感じられます。確かに.NETのLINQでは、副作用のあるアクションでTakeWhileを使用するのは不適切です。
Jon Skeet、2014

9

java8 + rxjavaを使用できます。

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));

8
Java 9は、ストリームでのtakeWhile操作のサポートを提供します。
pisaruk

6

並列操作で最大のパフォーマンスを得るには、findFirst()と同様のfindAny()を使用します。

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

ただし、安定した結果が必要な場合は、代わりにfindFirst()を使用してください。

また、一致するパターン(anyMatch()/ allMatch)はブール値のみを返すため、一致したオブジェクトは取得されません。


6

Java 9以降で更新takeWhile

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});

3

このようなことで達成しました

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }

3

これは、peek(..)とanyMatch(..)を組み合わせて使用​​して実現できます。

あなたの例を使用して:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

または、一般的なutilメソッドを記述します。

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

次のように使用します。

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});

0

これはどうですか:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

BooleanWrapperフローを制御するために実装する必要があるクラスはどこにありますか。


3
またはAtomicBoolean?
Koekje

1
これは、のとき!condition.ok()にオブジェクトの処理をスキップしますが、forEach()とにかくすべてのオブジェクトをループすることを防ぎません。遅い部分がforEach()反復であり、そのコンシューマーではない場合(たとえば、遅いネットワーク接続からオブジェクトを取得する場合)、このアプローチはあまり役に立ちません。
zakmck

はい、そうです、この場合、私の答えはかなり間違っています。
Thomas Decaux

0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

一致が見つかった場合にのみ操作を実行し、一致が見つかった場合は反復を停止します。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.