Passer à l'élément suivant à l'aide de la boucle foreach de Java 8 dans le flux

127

J'ai un problème avec le flux de Java 8 foreach qui tente de passer à l'élément suivant en boucle. Je ne peux pas définir la commande comme continue;, return;fonctionne seulement mais vous quitterez la boucle dans ce cas. J'ai besoin de passer à l'élément suivant en boucle. Comment puis je faire ça?

Exemple (ne fonctionne pas):

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

Exemple (fonctionnant):

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.
la source
Veuillez poster un exemple complet mais minimal pour qu'il soit plus facile de répondre.
Tunaki
J'ai besoin de passer à l'élément suivant en boucle.
Viktor V.
2
Son point est, avec le code que vous avez montré, ne pas avoir le continueserait toujours passer à l'élément suivant sans aucun changement fonctionnel de toute façon.
DoubleDouble
Si le but est d'éviter d'exécuter des lignes venant après l'appel pour continuer, et que vous ne nous montrez pas, mettez simplement toutes ces lignes dans un elsebloc. S'il n'y a rien après continue, alors supprimez le bloc if et continuez: ils sont inutiles.
JB Nizet

Réponses:

279

L'utilisation return;fonctionnera très bien. Cela n'empêchera pas la boucle complète de se terminer. Il arrêtera seulement d'exécuter l'itération actuelle de la forEachboucle.

Essayez le petit programme suivant:

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

Production:

un
c

Notez comment le return;est exécuté pour l' bitération, mais cimprime très bien sur l'itération suivante.

Pourquoi ça marche?

La raison pour laquelle le comportement semble peu intuitif au début est que nous sommes habitués à ce que l' returninstruction interrompe l'exécution de toute la méthode. Donc, dans ce cas, nous nous attendons à ce que l' mainexécution de la méthode dans son ensemble soit interrompue.

Cependant, ce qu'il faut comprendre, c'est qu'une expression lambda, telle que:

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

    System.out.println(str);
}

... doit vraiment être considérée comme sa propre «méthode» distincte, complètement distincte de la mainméthode, bien qu'elle soit commodément située en son sein. Donc, vraiment, l' returninstruction arrête uniquement l'exécution de l'expression lambda.

La deuxième chose à comprendre est que:

stringList.stream().forEach()

... n'est en réalité qu'une boucle normale sous les couvertures qui exécute l'expression lambda à chaque itération.

Avec ces 2 points à l'esprit, le code ci-dessus peut être réécrit de la manière équivalente suivante (à des fins éducatives uniquement):

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

Avec cet équivalent de code "moins magique", la portée de l' returninstruction devient plus apparente.

sstan
la source
3
J'ai déjà essayé de cette façon et pourquoi cela ne fonctionne pas, j'ai répété cette astuce et cela fonctionne. Merci, ça m'aide. Semble que je suis juste surmené =)
Viktor V.
2
Fonctionne comme un charme! Pourriez-vous ajouter une explication pourquoi cela fonctionne s'il vous plaît?
sparkyspider
2
@Spider: ajouté.
sstan
5

Autre solution: passez par un filtre avec vos conditions inversées: Exemple:

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

peut être remplacé par

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

L'approche la plus simple semble utiliser «retour»; bien que.

Anarkopsykotik
la source
2

Le lambda auquel vous passez forEach()est évalué pour chaque élément reçu du flux. L'itération elle-même n'est pas visible à partir de la portée du lambda, vous ne pouvez donc pas la continuefaire comme s'il forEach()s'agissait d'une macro de préprocesseur C. Au lieu de cela, vous pouvez ignorer conditionnellement le reste des instructions qu'il contient.

John Bollinger
la source
0

Vous pouvez utiliser une ifinstruction simple au lieu de continuer. Donc, au lieu de la façon dont vous avez votre code, vous pouvez essayer:

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

Le prédicat dans l'instruction if sera juste l'opposé du prédicat dans votre if(pred) continue;instruction, donc utilisez simplement !(logique non).

Hassan
la source