Pause ou retour du flux Java 8 pour chacun?


313

Lorsque vous utilisez une itération externe sur une boucle que Iterablenous utilisons breakou à returnpartir de pour chaque boucle améliorée comme:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

Comment pouvons-nous breakou returnutiliser l' itération interne dans une expression lambda Java 8 comme:

someObjects.forEach(obj -> {
   //what to do here?
})

6
Tu ne peux pas. Utilisez simplement une vraie fordéclaration.
Boann


Bien sûr , il est possible forEach(). La solution est pas agréable, mais il est possible. Voir ma réponse ci-dessous.
Honza Zidek

Considérez une autre approche, vous voulez juste ne pas exécuter de code , donc une simple ifcondition à l'intérieur du forEachfera l'affaire.
Thomas Decaux

Réponses:


374

Si vous en avez besoin, vous ne devriez pas utiliser forEach, mais l'une des autres méthodes disponibles sur les flux; lequel dépend de votre objectif.

Par exemple, si le but de cette boucle est de trouver le premier élément qui correspond à un prédicat:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

(Remarque: cela n'itérera pas toute la collection, car les flux sont évalués paresseusement - cela s'arrêtera au premier objet qui correspond à la condition).

Si vous voulez simplement savoir s'il y a un élément dans la collection pour lequel la condition est vraie, vous pouvez utiliser anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);

7
Cela fonctionne si la recherche d' un objet était l'objectif, mais cet objectif est généralement atteint par une returndéclaration d'une findSomethingméthode. breakest plus généralement associé à un temps de prise en charge.
Marko Topolnik

1
@MarkoTopolnik Oui, l'affiche originale ne nous a pas donné suffisamment d'informations pour savoir quel est exactement l'objectif; un "take while" est une troisième possibilité en plus des deux que j'ai mentionnées. (Existe-t-il un moyen simple de "prendre du temps" avec les flux?).
Jesper

3
Qu'en est-il lorsque l'objectif est d'implémenter correctement le comportement d'annulation? Est-ce que la meilleure chose que nous puissions faire est de simplement lancer une exception d'exécution à l'intérieur du lambda forEach lorsque nous remarquons que l'utilisateur a demandé une annulation?
user2163960

1
@HonzaZidek Edited, mais l'essentiel n'est pas de savoir si c'est possible ou non, mais quelle est la bonne façon de faire les choses. Vous ne devriez pas essayer de forcer l'utilisation forEachpour cela; vous devriez plutôt utiliser une autre méthode plus appropriée.
Jesper

1
@Jesper Je suis d'accord avec vous, j'ai écrit que je n'aimais pas la "solution d'exception". Cependant, votre formulation "Ce n'est pas possible avec forEach" était techniquement incorrecte. Je préfère également votre solution, mais je peux imaginer des cas d'utilisation où la solution fournie dans ma réponse est préférable: quand la boucle doit être terminée à cause d'une réelle exception. Je suis d'accord que vous ne devriez généralement pas utiliser les exceptions pour contrôler le flux.
Honza Zidek

54

Ceci est possible pour Iterable.forEach()(mais pas de manière fiable avec Stream.forEach()). La solution est pas agréable, mais il est possible.

AVERTISSEMENT : vous ne devez pas l'utiliser pour contrôler la logique métier, mais uniquement pour gérer une situation exceptionnelle qui se produit pendant l'exécution de la forEach(). Comme une ressource cesse soudainement d'être accessible, l'un des objets traités viole un contrat (par exemple, le contrat dit que tous les éléments du flux ne doivent pas l'être nullmais que soudainement et de manière inattendue l'un d'entre eux l'est null), etc.

Selon la documentation de Iterable.forEach():

Exécute l'action donnée pour chaque élément du Iterable jusqu'à ce que tous les éléments aient été traités ou que l'action lève une exception ... Les exceptions levées par l'action sont relayées à l'appelant.

Vous lancez donc une exception qui rompra immédiatement la boucle interne.

Le code sera quelque chose comme ça - je ne peux pas dire que je l'aime mais ça marche. Vous créez votre propre classe BreakExceptionqui s'étend RuntimeException.

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

Notez que le try...catchn'est pas autour de l'expression lambda, mais plutôt autour de la forEach()méthode entière . Pour le rendre plus visible, consultez la transcription suivante du code qui le montre plus clairement:

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

37
Je pense que c'est une mauvaise pratique et ne doit pas être considérée comme une solution au problème. C'est dangereux car cela peut être trompeur pour un débutant. Selon Effective Java 2nd Edition, Chapter 9, Item 57: `` Utiliser les exceptions uniquement pour des conditions exceptionnelles ''. En outre, "Utilisez des exceptions d'exécution pour indiquer les erreurs de programmation". En définitive, j'encourage fortement tous ceux qui envisagent cette solution à se pencher sur la solution @Jesper.
Louis F.

15
@LouisF. J'ai dit explicitement "je ne peux pas dire que j'aime ça mais ça marche". L'OP a demandé "comment rompre avec forEach ()" et ceci est une réponse. Je suis entièrement d'accord que cela ne devrait pas être utilisé pour contrôler la logique métier. Cependant, je peux imaginer des cas d'utilisation utiles, comme une connexion à une ressource soudainement non disponible au milieu de forEach () ou ainsi, pour laquelle l'utilisation d'exception n'est pas une mauvaise pratique. J'ai ajouté un paragraphe à ma réponse pour être clair.
Honza Zidek

1
Je pense que c'est une bonne solution. Après avoir recherché sur Google des "exceptions java" et d'autres recherches avec quelques mots comme "meilleures pratiques" ou "non vérifiées", etc., je vois qu'il y a une controverse sur la façon d'utiliser les exceptions. J'ai utilisé cette solution dans mon code car le flux effectuait une carte qui prendrait quelques minutes. Je voulais que l'utilisateur soit en mesure d'annuler la tâche, j'ai donc vérifié au début de chaque calcul l'indicateur "isUserCancelRequested" et j'ai levé une exception lorsque true. Il est propre, le code d'exception est isolé sur une petite partie du code et cela fonctionne.
Jason

2
Notez que Stream.forEachcela ne fournit pas la même garantie solide que les exceptions sont relayées à l'appelant, donc lever une exception n'est pas garanti pour fonctionner de cette façon Stream.forEach.
Radiodef

1
@Radiodef C'est un point valable, merci. Le message d'origine était sur le point Iterable.forEach(), mais j'ai ajouté votre point à mon texte juste pour être complet.
Honza Zidek

43

Un retour dans un lambda équivaut à une poursuite dans un pour chacun, mais il n'y a pas d'équivalent à une pause. Vous pouvez simplement faire un retour pour continuer:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})

2
Solution agréable et idiomatique pour répondre à l'exigence générale. La réponse acceptée extrapole l'exigence.
davidxxx

6
en d'autres termes, cela ne signifie pas "Interrompre ou revenir du flux Java 8 pour chacun", ce qui était la vraie question
Jaroslav Záruba

1
Cependant, cela "tirera" les enregistrements dans le flux source, ce qui est mauvais si vous effectuez une pagination dans une sorte de jeu de données distant.
Adrian Baker

23

Vous trouverez ci-dessous la solution que j'ai utilisée dans un projet. forEachUtilisez plutôt allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});

11

Soit vous devez utiliser une méthode qui utilise un prédicat indiquant s'il faut continuer (donc il a la pause à la place) ou vous devez lever une exception - ce qui est une approche très laide, bien sûr.

Vous pouvez donc écrire une forEachConditionalméthode comme celle-ci:

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

Au lieu de cela Predicate<T>, vous voudrez peut-être définir votre propre interface fonctionnelle avec la même méthode générale (quelque chose prenant un Tet retournant un bool) mais avec des noms qui indiquent l'attente plus clairement - Predicate<T>n'est pas idéal ici.


1
Je suggère qu'en réalité, l'utilisation de l'API Streams et d'une approche fonctionnelle est préférable à la création de cette méthode d'assistance si Java 8 est utilisé de toute façon.
skiwi

3
C'est l' takeWhileopération classique , et cette question n'est qu'une de celles qui montrent à quel point son manque dans l'API Streams se fait sentir.
Marko Topolnik

2
@Marko: takeWhile a plus l'impression que ce serait une opération produisant des éléments, sans effectuer d'action sur chacun. Certes, dans LINQ dans .NET, ce serait une mauvaise forme d'utiliser TakeWhile avec une action avec des effets secondaires.
Jon Skeet

9

Vous pouvez utiliser java8 + rxjava .

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));

8
Java 9 offrira la prise en charge de l'opération takeWhile sur les flux.
pisaruk

6

Pour des performances maximales dans les opérations parallèles, utilisez findAny () qui est similaire à findFirst ().

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

Cependant, si un résultat stable est souhaité, utilisez plutôt findFirst ().

Notez également que les modèles correspondants (anyMatch () / allMatch) renverront uniquement des booléens, vous n'obtiendrez pas d'objet correspondant.


6

Mettre à jour avec Java 9+ avec takeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});

3

J'ai réalisé quelque chose comme ça

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }

3

Vous pouvez y parvenir en utilisant un mélange de coup d'oeil (..) et anyMatch (..).

En utilisant votre exemple:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

Ou écrivez simplement une méthode d'utilisation générique:

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

Et puis utilisez-le, comme ceci:

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});

0

Et celui-ci:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

BooleanWrapperest une classe que vous devez implémenter pour contrôler le flux.


3
Ou AtomicBoolean?
Koekje

1
Cela ignore le traitement de l'objet lorsque !condition.ok(), cependant, cela n'empêche pas forEach()de boucler sur tous les objets de toute façon. Si la partie lente est l' forEach()itération et non son consommateur (par exemple, elle obtient des objets d'une connexion réseau lente), cette approche n'est pas très utile.
zakmck

Oui, vous avez raison, ma réponse est tout à fait erronée dans ce cas.
Thomas Decaux

0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

Il ne fera que l'opération où il trouvera la correspondance, et après la recherche de correspondance, il arrêtera son itération.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.