Interrompere o tornare dal flusso Java 8 per ogni?


313

Quando si utilizza l' iterazione esterna su un Iterableche utilizziamo breako returnda un ciclo avanzato per ogni come:

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

Come possiamo breako returnusare l' iterazione interna in un'espressione lambda di Java 8 come:

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

6
Non puoi. Usa solo una vera fordichiarazione.
Boann,


Certo che è possibile forEach(). La soluzione non è bella, ma è possibile. Vedi la mia risposta qui sotto.
Honza Zidek,

Considera un altro approccio, vuoi solo non eseguire il codice , quindi una semplice ifcondizione all'interno della forEachvolontà farà il trucco.
Thomas Decaux,

Risposte:


374

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 un predicato:

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

(Nota: questo non ripeterà l'intera raccolta, poiché 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, puoi usare anyMatch:

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

7
Questo funziona se la ricerca di un oggetto era l'obiettivo, ma tale obiettivo viene generalmente servito da returnun'istruzione di un findSomethingmetodo. breakè più comunemente associato a un take mentre -kind di operazione.
Marko Topolnik,

1
@MarkoTopolnik Sì, il poster originale non ci ha fornito informazioni sufficienti per sapere quale sia esattamente l'obiettivo; un "take while" è una terza possibilità oltre alle due che ho citato. (C'è un modo semplice per fare "prendere tempo" con i flussi?).
Jesper,

3
Che dire di quando l'obiettivo è implementare correttamente il comportamento di annullamento? È la cosa migliore che possiamo fare solo per lanciare un'eccezione di runtime all'interno di ogni lambda quando notiamo che l'utente ha richiesto un annullamento?
user2163960

1
@HonzaZidek Modificato, ma il punto non è se sia possibile o meno, ma quale sia il modo giusto di fare le cose. Non dovresti provare a forzare l'uso forEachper questo; dovresti invece usare un altro metodo più appropriato.
Jesper,

1
@Jesper Sono d'accordo con te, ho scritto che non mi piaceva la "Soluzione dell'eccezione". Tuttavia la tua dicitura "Questo non è possibile con forEach" era tecnicamente errata. Preferisco anche la tua soluzione, tuttavia posso immaginare casi d'uso in cui è preferibile la soluzione fornita nella mia risposta: quando il ciclo dovrebbe essere terminato a causa di una vera eccezione. Sono d'accordo che non dovresti generalmente usare le eccezioni per controllare il flusso.
Honza Zidek,

54

Questo è possibile per Iterable.forEach()(ma non in modo affidabile con Stream.forEach()). La soluzione non è bella, ma è possibile.

AVVERTENZA : non è necessario utilizzarlo per controllare la logica aziendale, ma esclusivamente per gestire una situazione eccezionale che si verifica durante l'esecuzione di forEach(). Ad esempio, quando una risorsa smette improvvisamente di essere accessibile, uno degli oggetti elaborati viola un contratto (ad esempio, il contratto afferma che tutti gli elementi nel flusso non devono essere nullma improvvisamente e inaspettatamente uno di essi è null) ecc.

Secondo la documentazione per Iterable.forEach():

Esegue l'azione specificata per ciascun elemento di Iterable fino a quando tutti gli elementi non sono stati elaborati o l'azione genera un'eccezione ... Le eccezioni generate dall'azione vengono inoltrate al chiamante.

Quindi si genera un'eccezione che interromperà immediatamente il ciclo interno.

Il codice sarà qualcosa del genere - non posso dire che mi piace ma funziona. Crea la tua classe BreakExceptionche si 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 il nontry...catch è attorno all'espressione lambda, ma piuttosto attorno all'intero metodo. Per renderlo più visibile, vedere la seguente trascrizione del codice che lo mostra più chiaramente:forEach()

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
Penso che questa sia una cattiva pratica e non dovrebbe essere considerata una soluzione al problema. È pericoloso perché potrebbe essere fuorviante per un principiante. Secondo Effective Java 2nd Edition, capitolo 9, punto 57: "Utilizzare le eccezioni solo per condizioni eccezionali". Inoltre "Utilizza le eccezioni di runtime per indicare errori di programmazione". In definitiva, incoraggio vivamente chiunque consideri questa soluzione a esaminare la soluzione @Jesper.
Louis F.

15
@LouisF. Ho detto esplicitamente "Non posso dire che mi piace ma funziona". L'OP ha chiesto "come uscire da forEach ()" e questa è una risposta. Sono pienamente d'accordo che questo non dovrebbe essere usato per controllare la logica aziendale. Tuttavia, posso immaginare alcuni utili casi d'uso, come ad esempio una connessione a una risorsa improvvisamente non disponibile nel mezzo di forEach () o giù di lì, per cui l'utilizzo dell'eccezione non è una cattiva pratica. Ho aggiunto un paragrafo alla mia risposta per essere chiaro.
Honza Zidek,

1
Penso che questa sia un'ottima soluzione. Dopo aver cercato su Google "eccezioni java" e altre ricerche con qualche parola in più come "best practice" o "non selezionata", ecc., Vedo che ci sono controversie su come utilizzare le eccezioni. Ho usato questa soluzione nel mio codice perché lo stream stava eseguendo una mappa che avrebbe richiesto minuti. Volevo che l'utente fosse in grado di annullare l'attività, quindi ho controllato all'inizio di ogni calcolo per il flag "isUserCancelRequested" e ho lanciato un'eccezione quando vero. È pulito, il codice di eccezione è isolato in una piccola parte del codice e funziona.
Jason,

2
Si noti che Stream.forEachnon non forniscono la stessa garanzia forte sulle eccezioni di essere trasmessa al chiamante, quindi un'eccezione, non è garantito il funzionamento in questo modo per Stream.forEach.
Radiodef,

1
@Radiodef Questo è un punto valido, grazie. Il post originale riguardava Iterable.forEach(), ma ho aggiunto il tuo punto al mio testo solo per completezza.
Honza Zidek,

43

Un ritorno in un lambda equivale a un proseguimento 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;
   }
})

2
Soluzione piacevole e idiomatica per rispondere al requisito generale. La risposta accettata estrapola il requisito.
davidxxx,

6
in altre parole questo non "interrompe o ritorna dal flusso Java 8 per ogni" che era la vera domanda
Jaroslav Záruba

1
Questo comunque "tirerà" i record attraverso il flusso di origine, il che è un male se si esegue il paging attraverso una sorta di set di dati remoto.
Adrian Baker,

23

Di seguito trovi la soluzione che ho usato in un progetto. Invece forEachusa solo allMatch:

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

11

O è necessario utilizzare un metodo che utilizza un predicato che indica se continuare (quindi ha invece l'interruzione) o è necessario lanciare un'eccezione, che è ovviamente un approccio molto brutto.

Quindi potresti scrivere un forEachConditionalmetodo 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 a Te restituisce a bool) ma con nomi che indicano più chiaramente le aspettative - Predicate<T>qui non è l'ideale.


1
Suggerirei che in realtà utilizzare l'API Streams e un approccio funzionale qui è preferibile alla creazione di questo metodo di supporto se Java 8 viene comunque utilizzato.
Skiwi,

3
Questa è l' takeWhileoperazione classica , e questa domanda non è che una di quelle che dimostrano quanto sia avvertita la sua mancanza nell'API Streams.
Marko Topolnik,

2
@Marko: takeWhile si sente più come se fosse un'operazione che produce oggetti, non eseguendo un'azione su ciascuno. Certamente in LINQ in .NET sarebbe scarsamente utile usare TakeWhile con un'azione con effetti collaterali.
Jon Skeet,

9

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

8
Java 9 offrirà supporto per l'operazione takeWhile su stream.
pisaruk,

6

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 pattern corrispondenti (anyMatch () / allMatch) restituiranno solo booleani, non si otterrà l'oggetto corrispondente.


6

Aggiornamento con Java 9+ con takeWhile:

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

3

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

3

Puoi farlo usando un mix di peek (..) e anyMatch (..).

Usando il tuo esempio:

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

O semplicemente scrivi 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 usalo, in questo modo:

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

0

Che dire di questo:

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

Dov'è BooleanWrapperuna classe che devi implementare per controllare il flusso.


3
O AtomicBoolean?
Koekje,

1
Ciò salta l'elaborazione degli oggetti quando !condition.ok(), tuttavia, non impedisce comunque forEach()di eseguire il ciclo su tutti gli oggetti. Se la parte lenta è l' forEach()iterazione e non il suo consumatore (ad esempio, ottiene oggetti da una connessione di rete lenta), questo approccio non è molto utile.
zakmck,

Sì, hai ragione, la mia risposta è piuttosto sbagliata in questo caso.
Thomas Decaux, il

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

Farà solo operazioni dove trova match, e dopo find match si ferma è iterazione.


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.