ストリームでJava 8 foreachループを使用して次のアイテムに移動する


126

ループ内の次の項目に移動しようとするJava 8 foreachのストリームに問題があります。コマンドをのようcontinue;に設定することはできません。return;機能するだけですが、この場合はループを終了します。ループの次の項目に移動する必要があります。どうやってやるの?

例(機能しない):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(...)
              continue; // this command doesn't working here
    });
}

例(動作):

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
    filteredLines = lines.filter(...).collect(Collectors.toList());
}

for(String filteredLine : filteredLines){
   ...
   if(...)
      continue; // it's working!
}

答えやすいように、完全で最小限の例を投稿してください。
ツナキ

ループ内の次のアイテムに移動する必要があります。
Viktor V.

2
彼のポイントは、あなたが示したコードでは、continueいずれにせよ機能的な変更なしに次の項目に移動することはできないということです。
DoubleDouble

継続の呼び出しの後に来る行の実行を回避することが目的であり、表示されない場合は、これらの行をすべてelseブロックに入れてください。の後continueに何もない場合は、ifブロックとcontinueをドロップします。それらは役に立たないのです。
JBニゼット2015

回答:


278

を使用しても問題return;なく動作します。完全なループの完了を妨げることはありません。forEachループの現在の反復の実行のみを停止します。

次の小さなプログラムを試してください:

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    stringList.stream().forEach(str -> {
        if (str.equals("b")) return; // only skips this iteration.

        System.out.println(str);
    });
}

出力:

a
c

return;b反復に対してどのように実行されるかに注意してください。ただしc、次の反復では問題なく出力されます。

なぜこれが機能するのですか?

最初は動作が直感的に思えない理由は、returnメソッド全体の実行を中断するステートメントに慣れているためです。したがって、この場合は、mainメソッドの実行全体が停止することが予想されます。

ただし、理解する必要があるのは、次のようなラムダ式です。

str -> {
    if (str.equals("b")) return;

    System.out.println(str);
}

...実際には、mainメソッド内に配置されているにもかかわらず、メソッドとは完全に別の独自の「メソッド」と見なす必要があります。したがって、実際には、returnステートメントはラムダ式の実行のみを停止します。

理解する必要がある2番目のことは、次のとおりです。

stringList.stream().forEach()

...は実際には、すべての反復でラムダ式を実行する隠れた通常のループです。

これらの2つの点を念頭に置いて、上記のコードは次の同等の方法で書き直すことができます(教育目的のみ)。

public static void main(String[] args) {
    ArrayList<String> stringList = new ArrayList<>();
    stringList.add("a");
    stringList.add("b");
    stringList.add("c");

    for(String s : stringList) {
        lambdaExpressionEquivalent(s);
    }
}

private static void lambdaExpressionEquivalent(String str) {
    if (str.equals("b")) {
        return;
    }

    System.out.println(str);
}

この「魔法の少ない」コードと同等のものを使用すると、returnステートメントの範囲がより明確になります。


3
私はすでにこの方法を試しましたが、なぜそれがうまくいかないのか、このトリックをもう一度繰り返してうまくいきました。ありがとう、それは私を助けます。私はただ過労しているようです=)
Viktor V.

2
魅力的な作品!これが機能する理由を説明を追加していただけませんか?
sparkyspider 2017年

2
@スパイダー:追加されました。
スタン、

5

別の解決策:反転した条件でフィルターを通過する:例:

if(subscribtion.isOnce() && subscribtion.isCalled()){
                continue;
}

と置き換えることができます

.filter(s -> !(s.isOnce() && s.isCalled()))

最も簡単なアプローチは、「リターン」を使用することです。でも。


2

渡したラムダはforEach()、ストリームから受け取った要素ごとに評価されます。あなたができないように反復自体は、ラムダの範囲内から見えないcontinueことかのようにforEach()Cプリプロセッサマクロました。代わりに、条件付きで残りのステートメントをスキップできます。


0

if続行する代わりに単純なステートメントを使用できます。したがって、コードの方法の代わりに、次のことを試すことができます。

try(Stream<String> lines = Files.lines(path, StandardCharsets.ISO_8859_1)){
            filteredLines = lines.filter(...).foreach(line -> {
           ...
           if(!...) {
              // Code you want to run
           }
           // Once the code runs, it will continue anyway
    });
}

ifステートメントの述語は、if(pred) continue;ステートメントの述語と正反対になるため、!(論理not)を使用してください。

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