Unterbrechen oder von Java 8 Stream für jeden zurückkehren?


313

Wenn Sie eine externe Iteration über eine verwenden, verwenden Iterablewir breakoder eine returnerweiterte für jede Schleife als:

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

Wie können breakoder returnverwenden wir die interne Iteration in einem Java 8-Lambda-Ausdruck wie:

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

6
Das kannst du nicht. Verwenden Sie einfach eine echte forAussage.
Boann


Natürlich ist es möglich für forEach(). Die Lösung ist nicht schön, aber es ist möglich. Siehe meine Antwort unten.
Honza Zidek

Betrachten Sie einen anderen Ansatz, Sie möchten nur keinen Code ausführen , also reicht eine einfache ifBedingung innerhalb des forEachWillens aus.
Thomas Decaux

Antworten:


374

Wenn Sie dies benötigen, sollten Sie nicht verwenden forEach eine der anderen in Streams verfügbaren Methoden verwenden. Welches, hängt davon ab, was Ihr Ziel ist.

Wenn das Ziel dieser Schleife beispielsweise darin besteht, das erste Element zu finden, das einem Prädikat entspricht:

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

(Hinweis: Dadurch wird nicht die gesamte Sammlung iteriert, da Streams träge ausgewertet werden. Es wird beim ersten Objekt angehalten, das der Bedingung entspricht.)

Wenn Sie nur wissen möchten, ob die Sammlung ein Element enthält, für das die Bedingung erfüllt ist, können Sie Folgendes verwenden anyMatch:

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

7
Dies funktioniert, wenn das Finden eines Objekts das Ziel war, dieses Ziel jedoch normalerweise durch eine returnAnweisung einer findSomethingMethode erreicht wird. breakist in der Regel mit einer Aufnahme während der Bedienung verbunden.
Marko Topolnik

1
@MarkoTopolnik Ja, das Originalplakat hat uns nicht genügend Informationen gegeben, um genau zu wissen, was das Ziel ist. Eine "Pause" ist eine dritte Möglichkeit neben den beiden, die ich erwähnt habe. (Gibt es eine einfache Möglichkeit, mit Streams "while while" zu machen?)
Jesper

3
Was ist, wenn das Ziel darin besteht, das Abbruchverhalten ordnungsgemäß zu implementieren? Ist das Beste, was wir tun können, um eine Laufzeitausnahme im forEach-Lambda auszulösen, wenn wir feststellen, dass der Benutzer einen Abbruch angefordert hat?
user2163960

1
@ HonzaZidek Bearbeitet, aber der Punkt ist nicht, ob es möglich ist oder nicht, sondern was der richtige Weg ist, Dinge zu tun. Sie sollten nicht versuchen, die Verwendung forEachdafür zu erzwingen . Sie sollten stattdessen eine andere, geeignetere Methode verwenden.
Jesper

1
@Jesper Ich stimme dir zu, ich schrieb, dass mir die "Ausnahmelösung" nicht gefallen hat. Ihre Formulierung "Dies ist mit nicht möglich forEach" war jedoch technisch falsch. Ich bevorzuge auch Ihre Lösung, kann mir jedoch Anwendungsfälle vorstellen, in denen die in meiner Antwort angegebene Lösung vorzuziehen ist: Wenn die Schleife aufgrund einer echten Ausnahme beendet werden sollte. Ich bin damit einverstanden, dass Sie die Ausnahmen im Allgemeinen nicht zur Steuerung des Flusses verwenden sollten.
Honza Zidek

54

Dies ist möglich für Iterable.forEach()(aber nicht zuverlässig mit Stream.forEach()). Die Lösung ist nicht schön, aber es ist möglich.

WARNUNG : Sie sollten es nicht zur Steuerung der Geschäftslogik verwenden, sondern nur zur Behandlung einer Ausnahmesituation, die während der Ausführung der forEach(). Wenn eine Ressource plötzlich nicht mehr zugänglich ist, verstößt eines der verarbeiteten Objekte gegen einen Vertrag (z. B. besagt der Vertrag, dass nicht alle Elemente im Stream vorhanden sein dürfen, nullsondern plötzlich und unerwartet eines davonnull ) usw.

Gemäß der Dokumentation für Iterable.forEach():

Führt die angegebene Aktion für jedes Element von aus, Iterable bis alle Elemente verarbeitet wurden oder die Aktion eine Ausnahme auslöst ... Von der Aktion ausgelöste Ausnahmen werden an den Aufrufer weitergeleitet.

Sie lösen also eine Ausnahme aus, die die interne Schleife sofort unterbricht.

Der Code wird ungefähr so ​​sein - ich kann nicht sagen, dass er mir gefällt, aber er funktioniert. Sie erstellen Ihre eigene Klasse, BreakExceptiondie erweitert wird 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
}

Beachten Sie, dass try...catches sich nicht um den Lambda-Ausdruck handelt, sondern um die gesamte forEach()Methode. Um es besser sichtbar zu machen, lesen Sie die folgende Transkription des Codes, die es deutlicher zeigt:

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
Ich denke, dies ist eine schlechte Praxis und sollte nicht als Lösung für das Problem angesehen werden. Es ist gefährlich, da es für einen Anfänger irreführend sein kann. Gemäß Effective Java 2nd Edition, Kapitel 9, Punkt 57: "Ausnahmen nur für außergewöhnliche Bedingungen verwenden". Außerdem 'Laufzeitausnahmen verwenden, um Programmierfehler anzuzeigen'. Auf jeden Fall empfehle ich jedem, der diese Lösung in Betracht zieht, die @ Jesper-Lösung zu prüfen.
Louis F.

15
@ LouisF. Ich sagte ausdrücklich "Ich kann nicht sagen, dass es mir gefällt, aber es funktioniert". Das OP fragte "wie man von forEach () abbricht" und dies ist eine Antwort. Ich stimme voll und ganz zu, dass dies nicht zur Kontrolle der Geschäftslogik verwendet werden sollte. Ich kann mir jedoch einige nützliche Anwendungsfälle vorstellen, wie zum Beispiel, dass eine Verbindung zu einer Ressource in der Mitte von forEach () oder so plötzlich nicht mehr verfügbar ist, für die die Verwendung von Ausnahmen keine schlechte Praxis ist. Ich habe meiner Antwort einen Absatz hinzugefügt, um klar zu sein.
Honza Zidek

1
Ich denke, das ist eine gute Lösung. Nachdem ich Google nach "Java-Ausnahmen" und anderen Suchanfragen mit ein paar weiteren Wörtern wie "Best Practices" oder "ungeprüft" usw. durchsucht habe, sehe ich Kontroversen über die Verwendung von Ausnahmen. Ich habe diese Lösung in meinem Code verwendet, weil der Stream eine Karte ausführte, die Minuten dauern würde. Ich wollte, dass der Benutzer die Aufgabe abbrechen kann, also habe ich zu Beginn jeder Berechnung nach dem Flag "isUserCancelRequested" gesucht und eine Ausnahme ausgelöst, wenn sie wahr ist. Es ist sauber, der Ausnahmecode ist auf einen kleinen Teil des Codes beschränkt und es funktioniert.
Jason

2
Beachten Sie, dass Stream.forEachdies nicht die gleiche starke Garantie für die Weiterleitung von Ausnahmen an den Anrufer bietet. Daher kann nicht garantiert werden, dass das Auslösen einer Ausnahme auf diese Weise funktioniert Stream.forEach.
Radiodef

1
@ Radiodef Das ist ein gültiger Punkt, danke. Der ursprüngliche Beitrag war ungefähr Iterable.forEach(), aber ich habe Ihren Punkt nur der Vollständigkeit halber zu meinem Text hinzugefügt.
Honza Zidek

43

Eine Rendite in einem Lambda entspricht einer Fortsetzung in einem For-Each, aber es gibt kein Äquivalent zu einer Pause. Sie können einfach zurückkehren, um fortzufahren:

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

2
Schöne und idiomatische Lösung, um die allgemeinen Anforderungen zu erfüllen. Die akzeptierte Antwort extrapoliert die Anforderung.
Davidxxx

6
Mit anderen Worten, dies bedeutet nicht "Break or Return from Java 8 Stream forEach", was die eigentliche Frage war
Jaroslav Záruba

1
Dadurch werden jedoch weiterhin Datensätze durch den Quelldatenstrom "gezogen", was schlecht ist, wenn Sie durch eine Art Remote-Dataset blättern.
Adrian Baker

23

Unten finden Sie die Lösung, die ich in einem Projekt verwendet habe. forEachVerwenden Sie stattdessen einfach allMatch:

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

11

Entweder müssen Sie eine Methode verwenden, die ein Prädikat verwendet, das angibt, ob Sie weitermachen möchten (also hat es stattdessen die Pause) oder Sie müssen eine Ausnahme auslösen - was natürlich ein sehr hässlicher Ansatz ist.

Sie könnten also eine forEachConditionalMethode wie diese schreiben :

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

Stattdessen Predicate<T>möchten Sie möglicherweise Ihre eigene funktionale Schnittstelle mit derselben allgemeinen Methode definieren (etwas, das a nimmt Tund a zurückgibt bool), aber mit Namen, die die Erwartung deutlicher anzeigen - Predicate<T>ist hier nicht ideal.


1
Ich würde vorschlagen, dass die Verwendung der Streams-API und eines funktionalen Ansatzes hier der Erstellung dieser Hilfsmethode vorzuziehen ist, wenn Java 8 trotzdem verwendet wird.
Skiwi

3
Dies ist die klassische takeWhileOperation, und diese Frage ist nur eine davon, die zeigt, wie sehr das Fehlen der Streams-API zu spüren ist.
Marko Topolnik

2
@Marko: takeWhile fühlt sich eher so an, als wäre es eine Operation, die Gegenstände liefert und nicht für jede eine Aktion ausführt. Sicherlich wäre es in LINQ in .NET eine schlechte Form, TakeWhile mit einer Aktion mit Nebenwirkungen zu verwenden.
Jon Skeet

9

Sie können java8 + rxjava verwenden .

//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 bietet Unterstützung für den TakeWhile-Betrieb in Streams.
Pisaruk

6

Verwenden Sie für maximale Leistung bei parallelen Operationen findAny (), ähnlich wie findFirst ().

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

Wenn jedoch ein stabiles Ergebnis gewünscht wird, verwenden Sie stattdessen findFirst ().

Beachten Sie auch, dass übereinstimmende Muster (anyMatch () / allMatch) nur boolesche Werte zurückgeben. Sie erhalten kein übereinstimmendes Objekt.


6

Update mit Java 9+ mit takeWhile:

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

3

Ich habe durch so etwas erreicht

  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

Sie können dies mit einer Mischung aus Peek (..) und anyMatch (..) erreichen.

Anhand Ihres Beispiels:

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

Oder schreiben Sie einfach eine generische util-Methode:

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

Und dann benutze es so:

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

0

Was ist mit diesem:

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

Wo BooleanWrapperist eine Klasse, die Sie implementieren müssen, um den Fluss zu steuern?


3
Oder AtomicBoolean?
Koekje

1
Dies überspringt die Objektverarbeitung, wenn !condition.ok()jedoch forEach()ohnehin nicht verhindert wird, dass alle Objekte durchlaufen werden. Wenn der langsame Teil die forEach()Iteration und nicht der Verbraucher ist (z. B. Objekte von einer langsamen Netzwerkverbindung abruft), ist dieser Ansatz nicht sehr nützlich.
Zakmck

Ja, Sie haben Recht, meine Antwort ist in diesem Fall völlig falsch.
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;
});

Es wird nur dort ausgeführt, wo eine Übereinstimmung gefunden wird, und nach der Suche nach Übereinstimmung wird die Iteration gestoppt.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.