Passa all'elemento successivo utilizzando Java 8 foreach loop nel flusso


126

Ho un problema con il flusso di Java 8 foreach che tenta di passare all'elemento successivo in loop. Non riesco a impostare il comando come continue;, return;funziona solo ma in questo caso uscirai dal ciclo. Devo passare all'elemento successivo in loop. Come lo posso fare?

Esempio (non funzionante):

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

Esempio (di lavoro):

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!
}

Pubblica un esempio completo ma minimale in modo che sia più facile rispondere.
Tunaki

Devo passare all'elemento successivo in loop.
Viktor V.

2
Il suo punto è che, con il codice che hai mostrato, non continuesarebbe comunque possibile passare all'elemento successivo senza modifiche funzionali.
DoubleDouble

Se l'obiettivo è evitare di eseguire linee successive alla chiamata per continuare e che non ci stai mostrando, metti tutte queste linee in un elseblocco. Se non c'è niente dopo continue, rilascia il blocco if e il continue: sono inutili.
JB Nizet

Risposte:


278

L'utilizzo return;funzionerà benissimo. Non impedirà il completamento del ciclo completo. Smetterà solo di eseguire l'iterazione corrente del forEachciclo.

Prova il seguente programmino:

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

Produzione:

a
c

Si noti come return;viene eseguito per l' biterazione, ma viene cstampato correttamente nell'iterazione successiva.

Perché funziona?

Il motivo per cui il comportamento all'inizio sembra poco intuitivo è perché siamo abituati returnall'istruzione che interrompe l'esecuzione dell'intero metodo. Quindi, in questo caso, ci aspettiamo che l' mainesecuzione del metodo nel suo insieme venga interrotta.

Tuttavia, ciò che deve essere compreso è che un'espressione lambda, come:

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

    System.out.println(str);
}

... ha davvero bisogno di essere considerato come il suo "metodo" distinto, completamente separato dal mainmetodo, nonostante sia convenientemente situato al suo interno. Quindi, in realtà, l' returnistruzione interrompe solo l'esecuzione dell'espressione lambda.

La seconda cosa che deve essere compresa è che:

stringList.stream().forEach()

... è in realtà solo un normale ciclo nascosto che esegue l'espressione lambda per ogni iterazione.

Con questi 2 punti in mente, il codice sopra può essere riscritto nel seguente modo equivalente (solo per scopi didattici):

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

Con questo equivalente di codice "meno magico", l'ambito returndell'istruzione diventa più evidente.


3
Ho già provato in questo modo e per qualche motivo non funziona, ho ripetuto di nuovo questo trucco e funziona. Grazie, mi aiuta. Sembra che io sia solo oberato di lavoro =)
Viktor V.

2
Funziona come un fascino! Potresti aggiungere una spiegazione del perché funziona, per favore?
Sparkyspider

2
@ Spider: aggiunto.
sstan

5

Un'altra soluzione: passa attraverso un filtro con le tue condizioni invertite: Esempio:

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

può essere sostituito con

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

L'approccio più diretto sembra utilizzare il "ritorno"; anche se.


2

Il lambda a cui stai passando forEach()viene valutato per ogni elemento ricevuto dal flusso. L'iterazione stessa non è visibile dall'ambito del lambda, quindi non è possibile continueeseguirla come se forEach()fosse una macro del preprocessore C. Invece, puoi saltare condizionatamente il resto delle dichiarazioni in esso contenute.


0

Puoi usare una semplice ifdichiarazione invece di continuare. Quindi, invece del modo in cui hai il tuo codice, puoi provare:

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

Il predicato nell'istruzione if sarà esattamente l'opposto del predicato nella tua if(pred) continue;istruzione, quindi usa !(non logico).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.