Przejdź do następnego elementu, używając w strumieniu pętli foreach Java 8

126

Mam problem ze strumieniem Java 8 foreach próbującym przejść do następnego elementu w pętli. Nie mogę ustawić polecenia jak continue;, return;działa tylko, ale w tym przypadku wyjdziesz z pętli. Muszę przejść do następnego elementu w pętli. Jak mogę to zrobić?

Przykład (nie działa):

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

Przykład (działający):

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.
źródło
Prześlij pełny, ale minimalny przykład, aby ułatwić odpowiedź.
Tunaki,
Muszę przejść do następnego elementu w pętli.
Viktor V.
2
Chodzi mu o to, że z kodem, który pokazałeś, i tak nie continueprzejście do następnego elementu bez żadnych zmian funkcjonalnych.
DoubleDouble
Jeśli celem jest uniknięcie wykonywania linii przychodzących po wywołaniu w celu kontynuacji i nie pokazujesz nam, po prostu umieść wszystkie te linie w elsebloku. Jeśli nie ma nic później continue, porzuć blok if i kontynuuj: są bezużyteczne.
JB Nizet

Odpowiedzi:

278

Używanie return;będzie działać dobrze. Nie przeszkodzi to w ukończeniu pełnej pętli. Przestanie tylko wykonywać bieżącą iterację forEachpętli.

Wypróbuj następujący mały program:

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

Wynik:

a
c

Zwróć uwagę, jak return;jest wykonywany dla biteracji, ale cdrukuje w następnej iteracji dobrze.

Dlaczego to działa?

Powodem, dla którego zachowanie wydaje się początkowo nieintuicyjne, jest to, że jesteśmy przyzwyczajeni do returninstrukcji przerywającej wykonanie całej metody. W tym przypadku spodziewamy się, że mainwykonanie metody jako całości zostanie zatrzymane.

Należy jednak zrozumieć, że wyrażenie lambda, takie jak:

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

    System.out.println(str);
}

... naprawdę należy traktować jako swoją własną, odrębną „metodę”, całkowicie odrębną od mainmetody, mimo że jest dogodnie umiejscowiona w jej obrębie. Tak naprawdę returninstrukcja zatrzymuje tylko wykonanie wyrażenia lambda.

Drugą rzeczą, którą należy zrozumieć, jest to, że:

stringList.stream().forEach()

... jest po prostu zwykłą pętlą pod okładkami, która wykonuje wyrażenie lambda dla każdej iteracji.

Mając na uwadze te 2 punkty, powyższy kod można przepisać w następujący równoważny sposób (tylko w celach edukacyjnych):

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

Dzięki temu „mniej magicznemu” odpowiednikowi kodu zakres returninstrukcji staje się bardziej widoczny.

sstan
źródło
3
Próbowałem już w ten sposób i dlaczego nie działa, powtórzyłem tę sztuczkę ponownie i działa. Dzięki, to mi pomaga. Wygląda na to, że jestem po prostu przepracowany =)
Viktor V.
2
Działa jak marzenie! Czy możesz dodać wyjaśnienie, dlaczego to działa?
sparkyspider
2
@Spider: dodano.
sstan
5

Inne rozwiązanie: przejdź przez filtr z odwróconymi warunkami: Przykład:

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

można zastąpić

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

Wydaje się, że najprostszym podejściem jest użycie „powrotu”; chociaż.

Anarkopsykotik
źródło
2

Lambda, do której przechodzisz, forEach()jest oceniana dla każdego elementu otrzymanego ze strumienia. Sama iteracja nie jest widoczna z zakresu lambda, więc nie można continuejej tak, jakby forEach()była makrem preprocesora C. Zamiast tego możesz warunkowo pominąć pozostałe zawarte w nim instrukcje.

John Bollinger
źródło
0

Możesz użyć prostej ifinstrukcji zamiast kontynuować. Więc zamiast sposobu, w jaki masz swój kod, możesz spróbować:

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

Predykat w instrukcji if będzie po prostu przeciwieństwem predykatu w if(pred) continue;instrukcji, więc po prostu użyj !(nie logiczne).

Hassan
źródło