Mit Java 8 foreach-Schleife im Stream zum nächsten Element wechseln

126

Ich habe ein Problem mit dem Stream von Java 8 für jeden Versuch, mit dem nächsten Element in der Schleife fortzufahren. Ich kann den Befehl nicht so einstellen continue;, return;funktioniert nur, aber in diesem Fall verlassen Sie die Schleife. Ich muss mit dem nächsten Element in der Schleife fortfahren. Wie kann ich das machen?

Beispiel (funktioniert nicht):

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

Beispiel (funktioniert):

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.
quelle
Bitte posten Sie ein vollständiges, aber minimales Beispiel, damit die Beantwortung einfacher ist.
Tunaki
Ich muss mit dem nächsten Element in der Schleife fortfahren.
Viktor V.
2
Sein Punkt ist, dass mit dem Code, den Sie gezeigt haben, das nicht continueimmer noch ohne funktionale Änderungen zum nächsten Element übergehen würde.
DoubleDouble
Wenn das Ziel darin besteht, zu vermeiden, dass nach dem Anruf kommende Zeilen ausgeführt werden, um fortzufahren, und dass Sie uns nicht anzeigen, setzen Sie einfach alle diese Zeilen in einen elseBlock. Wenn es danach nichts mehr gibt continue, lassen Sie den if-Block fallen und fahren Sie fort: Sie sind nutzlos.
JB Nizet

Antworten:

277

Die Verwendung return;funktioniert einwandfrei. Es wird nicht verhindert, dass die vollständige Schleife abgeschlossen wird. Die aktuelle Iteration der forEachSchleife wird nur nicht mehr ausgeführt .

Probieren Sie das folgende kleine Programm aus:

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

Ausgabe:

a
c

Beachten Sie, wie das return;für die bIteration ausgeführt wird, aber cauf der folgenden Iteration gut gedruckt wird.

Warum funktioniert das?

Der Grund, warum das Verhalten zunächst nicht intuitiv erscheint, ist, dass wir es gewohnt sind, dass die returnAnweisung die Ausführung der gesamten Methode unterbricht. In diesem Fall erwarten wir, dass die maingesamte Methodenausführung angehalten wird.

Was jedoch verstanden werden muss, ist, dass ein Lambda-Ausdruck wie:

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

    System.out.println(str);
}

... muss wirklich als eine eigene "Methode" betrachtet werden, die völlig unabhängig von der mainMethode ist, obwohl sie sich bequem darin befindet. Die returnAnweisung stoppt also nur die Ausführung des Lambda-Ausdrucks.

Das zweite, was verstanden werden muss, ist Folgendes:

stringList.stream().forEach()

... ist wirklich nur eine normale Schleife unter der Decke, die den Lambda-Ausdruck für jede Iteration ausführt.

Unter Berücksichtigung dieser beiden Punkte kann der obige Code auf folgende äquivalente Weise umgeschrieben werden (nur zu Bildungszwecken):

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

Mit diesem "weniger magischen" Code-Äquivalent wird der Umfang der returnAnweisung deutlicher.

sstan
quelle
3
Ich habe es bereits versucht und irgendwie funktioniert es nicht. Ich habe diesen Trick noch einmal wiederholt und es funktioniert. Danke, es hilft mir. Scheint, ich bin gerade überarbeitet =)
Viktor V.
2
Klappt wunderbar! Könnten Sie bitte eine Erklärung hinzufügen, warum dies funktioniert?
Sparkyspider
2
@Spider: hinzugefügt.
Sstan
5

Eine andere Lösung: Gehen Sie mit Ihren invertierten Bedingungen durch einen Filter: Beispiel:

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

kann durch ersetzt werden

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

Der einfachste Ansatz scheint die Verwendung von "return" zu sein. obwohl.

Anarkopsykotik
quelle
2

Das Lambda, an das Sie übergeben, forEach()wird für jedes vom Stream empfangene Element ausgewertet. Die Iteration selbst ist im Bereich des Lambda nicht sichtbar, daher können Sie sie nicht continuewie forEach()ein C-Präprozessor-Makro anzeigen. Stattdessen können Sie den Rest der darin enthaltenen Anweisungen bedingt überspringen.

John Bollinger
quelle
0

Sie können eine einfache ifAnweisung verwenden, anstatt fortzufahren. Anstelle der Art und Weise, wie Sie Ihren Code haben, können Sie Folgendes versuchen:

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

Das Prädikat in der if-Anweisung ist genau das Gegenteil des Prädikats in Ihrer if(pred) continue;Anweisung. Verwenden Sie also einfach !(logisch nicht).

Hassan
quelle