Pause ou retour de Java 8 stream forEach?
Lors de l'utilisation de externe itération sur une Iterable
nous utilisons break
ou return
renforcée pour chaque boucle:
for (SomeObject obj : someObjects) {
if (some_condition_met) {
break; // or return obj
}
}
Comment pouvons-nous break
ou return
utiliser l'itération interne dans une expression Java 8 lambda comme:
someObjects.forEach(obj -> {
//what to do here?
})
11 answers
Si vous en avez besoin, vous ne devriez pas utiliser forEach
, mais l'une des autres méthodes disponibles sur streams; laquelle 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 - il s'arrêtera au premier objet qui correspond à la condition).
Si vous voulez juste savoir s'il y a un élément dans la collection pour laquelle la condition est vraie, vous pouvez utiliser anyMatch
:
boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
Ce est possible pour Iterable.forEach()
(mais pas de manière fiable avec Stream.forEach()
). La solution n'est pas agréable, mais c' est possible.
WARNING : Vous ne devez pas l'utiliser pour contrôler la logique métier, mais uniquement pour gérer une situation exceptionnelle qui se produit lors de l'exécution du forEach()
. Comme une ressource cesse soudainement d'être accessible, l'un des objets traités viole un contrat (par exemple, le contrat indique que tous les éléments du flux ne doivent pas être null
, mais soudainement et de façon inattendue, l'un d'eux est null
) etc.
, Selon la documentation pour Iterable.forEach()
:
Effectue l'action donnée, pour chaque élément de la
Iterable
jusqu', tous les éléments ont été traitées, ou l'action déclenche une exception... Les exceptions lancées par l'action sont relayées à l'appelant.
Vous lancez donc une exception qui cassera 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 BreakException
qui é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 la try...catch
est pas autour de l'expression lambda, mais plutôt autour de l'ensemble forEach()
méthode. Pour le rendre plus visible, voir 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
}
Un retour dans un lambda est égal à un continue dans un for-each, 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;
}
})
Vous trouverez ci-dessous la solution que j'ai utilisée dans un projet. Au lieu de cela forEach
utilisez simplement allMatch
:
someObjects.allMatch(obj -> {
return !some_condition_met;
});
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 méthode forEachConditional
comme ceci:
public static <T> void forEachConditional(Iterable<T> source,
Predicate<T> action) {
for (T item : source) {
if (!action.test(item)) {
break;
}
}
}
Plutôt que 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 T
et renvoyant un bool
) mais avec des noms qui indiquent l'attente plus clairement - Predicate<T>
n'est pas idéal ici.
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));
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 FindFirst() à la place.
Notez également que les modèles correspondants (anyMatch()/allMatch) ne renverront que booléen, vous n'obtiendrez pas d'objet correspondant.
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;
}
Vous pouvez y parvenir en utilisant un mélange de peek(..) et anyMatch(..).
En utilisant votre exemple:
someObjects.stream().peek(obj -> {
<your code here>
}).anyMatch(obj -> !<some_condition_met>);
Ou écrivez simplement une méthode util 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());
}
Puis utilisez-le, comme ceci:
streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
<your code here>
});
Ce n'est pas facile de le faire par les API fournies dans JDK. Mais nous pouvons le faire par AbacusUtil. Voici un exemple de code:
Stream.of("a", "b", "c", "d", "e").peek(N::println) //
.forEach("", (r, e) -> r + e, (e, r) -> e.equals("c"));
// print: a b c
Divulgation: Je suis le développeur de AbacusUtil.
Qu'en est-il de celui-ci:
Si vous avez besoin d'un BooleanWrapper, vous pouvez utiliser le BooleanWrapper.]}someObjects.forEach(obj -> {
if (condition.ok()) {
// YOUR CODE to control
condition.stop();
}
});
Où BooleanWrapper
est une classe que vous devez implémenter pour contrôler le flux.