Interruzione o ritorno dal flusso Java 8 forEach?
Quando si utilizza iterazione esterna su un Iterable
usiamo break
o return
da enhanced per-ogni ciclo come:
for (SomeObject obj : someObjects) {
if (some_condition_met) {
break; // or return obj
}
}
Come possiamo break
o return
usando l'iterazione interna in un'espressione lambda Java 8 come:
someObjects.forEach(obj -> {
//what to do here?
})
11 answers
Se ne hai bisogno, non dovresti usare forEach
, ma uno degli altri metodi disponibili sugli stream; quale, dipende da quale sia il tuo obiettivo.
Ad esempio, se l'obiettivo di questo ciclo è trovare il primo elemento che corrisponde a qualche predicato:
Optional<SomeObject> result =
someObjects.stream().filter(obj -> some_condition_met).findFirst();
(Nota: questo non itererà l'intera raccolta, perché i flussi vengono valutati pigramente - si fermerà al primo oggetto che corrisponde alla condizione).
Se vuoi solo sapere se c'è un elemento nella raccolta per cui la condizione è vera, è possibile utilizzare anyMatch
:
boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
Questo è possibile per Iterable.forEach()
(ma non in modo affidabile con Stream.forEach()
). La soluzione non è bella, ma è possibile.
ATTENZIONE: Non si dovrebbe usare per controllare la logica di business, ma puramente per la gestione di una situazione eccezionale che si verifica durante l'esecuzione del forEach()
. Ad esempio, una risorsa smette improvvisamente di essere accessibile, uno degli oggetti elaborati sta violando un contratto (ad es. null
ma improvvisamente e inaspettatamente uno di loro è null
) ecc.
Secondo la documentazione per Iterable.forEach()
:
Esegue l'azione data per ogni elemento del
Iterable
fino a quandotutti gli elementi sono stati elaborati o l'azione genera un'eccezione... Le eccezioni generate dall'azione vengono inoltrate al chiamante.
Quindi lanci un'eccezione che interromperà immediatamente il ciclo interno.
Il codice sarà qualcosa di simile - Non posso dire che mi piace ma funziona. Si crea la propria classe BreakException
che estende 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
}
Si noti che try...catch
è non attorno all'espressione lambda, ma piuttosto attorno all'intero metodo forEach()
. Per renderlo più visibile, vedere la seguente trascrizione del codice che lo mostra più chiaramente:
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 ritorno in un lambda equivale a una continua in un for-each, ma non c'è equivalente a una pausa. Puoi semplicemente fare un ritorno per continuare:
someObjects.forEach(obj -> {
if (some_condition_met) {
return;
}
})
Di seguito trovi la soluzione che ho usato in un progetto. Invece forEach
basta usare allMatch
:
someObjects.allMatch(obj -> {
return !some_condition_met;
});
O è necessario utilizzare un metodo che utilizza un predicato che indica se continuare (quindi ha invece la pausa) o è necessario lanciare un'eccezione - che è un approccio molto brutto, ovviamente.
Quindi potresti scrivere un metodo forEachConditional
come questo:
public static <T> void forEachConditional(Iterable<T> source,
Predicate<T> action) {
for (T item : source) {
if (!action.test(item)) {
break;
}
}
}
Piuttosto che Predicate<T>
, potresti voler definire la tua interfaccia funzionale con lo stesso metodo generale (qualcosa che prende un T
e restituisce un bool
) ma con nomi che indicano l'aspettativa più chiaramente - Predicate<T>
non è l'ideale qui.
Puoi usare 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));
Per prestazioni massime in operazioni parallele utilizzare findAny () che è simile a findFirst().
Optional<SomeObject> result =
someObjects.stream().filter(obj -> some_condition_met).findAny();
Tuttavia, se si desidera un risultato stabile, utilizzare invece findFirst ().
Si noti inoltre che i modelli corrispondenti (anyMatch()/allMatch) restituiranno solo booleano, non si otterrà un oggetto abbinato.
Ho raggiunto qualcosa di simile
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;
}
È possibile ottenere che utilizzando un mix di peek(..) e anyMatch(..).
Usando il tuo esempio:
someObjects.stream().peek(obj -> {
<your code here>
}).anyMatch(obj -> !<some_condition_met>);
O semplicemente scrivere un metodo util generico:
public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
stream.peek(consumer).anyMatch(predicate.negate());
}
E poi usarlo, in questo modo:
streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
<your code here>
});
Non è facile farlo con le API fornite in JDK. Ma possiamo farlo con AbacusUtil . Ecco il codice di esempio:
Stream.of("a", "b", "c", "d", "e").peek(N::println) //
.forEach("", (r, e) -> r + e, (e, r) -> e.equals("c"));
// print: a b c
Divulgazione: Sono lo sviluppatore di AbacusUtil.
Che dire di questo:
Condizione BooleanWrapper finale = nuovo BooleanWrapper();
someObjects.forEach(obj -> {
if (condition.ok()) {
// YOUR CODE to control
condition.stop();
}
});
Dove BooleanWrapper
è una classe che devi implementare per controllare il flusso.