Przerwać lub powrócić ze strumienia Java 8 dla każdego?


312

Podczas korzystania z zewnętrznej iteracji w pętli Iterableużywamy breaklub returnz rozszerzonej dla każdej pętli jako:

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

Jak możemy breaklub returnużywając wewnętrznej iteracji w wyrażeniu lambda Java 8, takim jak:

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

6
Nie możesz Po prostu użyj prawdziwego forstwierdzenia.
Boann


Oczywiście jest to możliwe forEach(). Rozwiązanie nie jest miłe, ale jest możliwe. Zobacz moją odpowiedź poniżej.
Honza Zidek

Rozważ inne podejście, po prostu nie chcesz wykonywać kodu , więc wystarczy prosty ifwarunek wewnątrz forEach.
Thomas Decaux

Odpowiedzi:


373

Jeśli potrzebujesz tego, nie powinieneś używać forEach, ale jednej z innych metod dostępnych w strumieniach; który zależy od tego, jaki jest twój cel.

Na przykład, jeśli celem tej pętli jest znalezienie pierwszego elementu, który pasuje do jakiegoś predykatu:

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

(Uwaga: nie spowoduje to iteracji całej kolekcji, ponieważ strumienie są leniwie oceniane - zatrzyma się na pierwszym obiekcie spełniającym warunek).

Jeśli chcesz tylko wiedzieć, czy w kolekcji jest element, dla którego warunek jest spełniony, możesz użyć anyMatch:

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

7
Działa to, jeśli celem było znalezienie obiektu, ale cel ten jest zwykle obsługiwany przez returninstrukcję z findSomethingmetody. breakjest zwykle kojarzony z operacją typu „ take while ”.
Marko Topolnik,

1
@MarkoTopolnik Tak, oryginalny plakat nie dał nam wystarczających informacji, aby wiedzieć, jaki dokładnie jest cel; „Weź chwilę” to trzecia możliwość oprócz dwóch, o których wspomniałem. (Czy istnieje prosty sposób na wykonanie „streamingu”?
Jesper

3
A kiedy celem jest prawidłowe wdrożenie zachowania anulowania? Czy najlepszą rzeczą, jaką możemy zrobić, jest zgłoszenie wyjątku czasu wykonywania wewnątrz lambda forEach, gdy zauważymy, że użytkownik zażądał anulowania?
user2163960

1
@HonzaZidek Edytowane, ale nie chodzi o to, czy jest to możliwe, czy nie, ale o to, co należy zrobić. Nie powinieneś próbować do tego zmuszać forEach; zamiast tego powinieneś użyć innej, bardziej odpowiedniej metody.
Jesper,

1
@Jesper Zgadzam się z tobą, napisałem, że nie podoba mi się „Rozwiązanie wyjątkowe”. Jednak twoje sformułowanie „To nie jest możliwe przy forEach” było technicznie niepoprawne. Preferuję również twoje rozwiązanie, ale mogę sobie wyobrazić przypadki użycia, w których rozwiązanie przedstawione w mojej odpowiedzi jest lepsze: kiedy pętla powinna zostać zakończona z powodu prawdziwego wyjątku. Zgadzam się, że ogólnie nie powinieneś używać wyjątków do kontrolowania przepływu.
Honza Zidek

53

Jest to możliwe Iterable.forEach()(ale nie w sposób niezawodny Stream.forEach()). Rozwiązanie nie jest miłe, ale jest możliwe.

OSTRZEŻENIE : Nie należy go używać do kontrolowania logiki biznesowej, lecz wyłącznie do obsługi wyjątkowej sytuacji, która występuje podczas wykonywania forEach(). Tak jak zasób nagle przestaje być dostępny, jeden z przetwarzanych obiektów narusza umowę (np. Umowa mówi, że wszystkie elementy w strumieniu nie mogą być, nullale nagle i nieoczekiwanie jednym z nich jest null) itp.

Zgodnie z dokumentacją dla Iterable.forEach():

Wykonuje podaną akcję dla każdego elementu, Iterable dopóki wszystkie elementy nie zostaną przetworzone lub akcja wygeneruje wyjątek ... Wyjątki zgłoszone przez akcję są przekazywane do osoby wywołującej.

Zgłaszasz wyjątek, który natychmiast przerwie wewnętrzną pętlę.

Kod będzie mniej więcej taki - nie mogę powiedzieć, że mi się podoba, ale działa. Tworzysz własną klasę, BreakExceptionktóra się rozszerza 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
}

Zauważ, że try...catchto nie wokół wyrażenie lambda, ale raczej po całym forEach()metoda. Aby uczynić go bardziej widocznym, zobacz następującą transkrypcję kodu, który pokazuje go wyraźniej:

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
Uważam, że jest to zła praktyka i nie należy jej uważać za rozwiązanie problemu. Jest to niebezpieczne, ponieważ dla początkującego może być mylące. Według Effective Java 2nd Edition, rozdział 9, pozycja 57: „Używaj wyjątków tylko w wyjątkowych warunkach”. Ponadto „Użyj wyjątków czasu wykonywania w celu wskazania błędów programowania”. Podsumowując, gorąco zachęcam każdego, kto rozważa to rozwiązanie, do zapoznania się z rozwiązaniem @Jesper.
Louis F.

15
@LouisF. Powiedziałem wprost: „Nie mogę powiedzieć, że mi się podoba, ale działa”. OP zapytał „jak zerwać z forEach ()” i to jest odpowiedź. W pełni zgadzam się, że nie należy tego używać do kontrolowania logiki biznesowej. Mogę jednak wyobrazić sobie kilka użytecznych przypadków użycia, takich jak połączenie z zasobem nagle niedostępne w środku forEach () lub podobne, dla których użycie wyjątku nie jest złą praktyką. Dla jasności dodałem akapit.
Honza Zidek

1
Myślę, że to dobre rozwiązanie. Po wyszukaniu w Google „wyjątków Java” i innych wyszukiwań z kilkoma dodatkowymi słowami, takimi jak „najlepsze praktyki” lub „niezaznaczone” itp., Widzę kontrowersje dotyczące sposobu korzystania z wyjątków. Użyłem tego rozwiązania w kodzie, ponieważ strumień wykonywał mapę, która zajęłaby minuty. Chciałem, aby użytkownik mógł anulować zadanie, więc sprawdziłem na początku każdego obliczenia dla flagi „isUserCancelRequested” i rzuciłem wyjątek, gdy jest prawdziwy. Jest czysty, kod wyjątku jest izolowany na małą część kodu i działa.
Jason

2
Zauważ, że Stream.forEachnie nie zapewniają taką samą silną gwarancję o wyjątkami są przekazywane do dzwoniącego, więc rzuca wyjątek nie jest gwarantowane do pracy tę drogę Stream.forEach.
Radiodef,

1
@Radiodef To jest ważny punkt, dzięki. Oryginalny post był o Iterable.forEach(), ale dodałem twój punkt do mojego tekstu tylko dla kompletności.
Honza Zidek,

43

Zwrot w lambda równa się kontynuacji w for-each, ale nie ma odpowiednika przerwy. Możesz po prostu wrócić, aby kontynuować:

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

2
Ładne i idiomatyczne rozwiązanie spełniające ogólne wymagania. Przyjęta odpowiedź ekstrapoluje wymóg.
davidxxx

6
innymi słowy, nie oznacza to „Przerwa lub powrót ze strumienia Java 8 dla każdego”, co było faktycznym pytaniem
Jaroslav Záruba

1
To jednak nadal „pobiera” rekordy przez strumień źródłowy, co jest złe, jeśli przeglądasz jakiś zdalny zestaw danych.
Adrian Baker

23

Poniżej znajdziesz rozwiązanie, którego użyłem w projekcie. Zamiast tego forEachużyj allMatch:

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

11

Albo musisz użyć metody, która korzysta z predykatu wskazującego, czy kontynuować (więc zamiast tego ma przerwę), albo musisz zgłosić wyjątek - co jest bardzo brzydkim podejściem.

Więc możesz napisać taką forEachConditionalmetodę:

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

Zamiast tego Predicate<T>możesz zdefiniować własny interfejs funkcjonalny za pomocą tej samej ogólnej metody (coś biorąc Ti zwracając a bool), ale z nazwami, które wyraźniej wskazują na oczekiwania - Predicate<T>nie jest tutaj idealne.


1
Sugerowałbym, że faktycznie użycie interfejsu Streams API i funkcjonalnego podejścia jest lepsze niż tworzenie tej metody pomocniczej, jeśli Java 8 jest w jakikolwiek sposób używana.
skiwi

3
Jest to klasyczna takeWhileoperacja, a to pytanie jest tylko jednym z tych, które pokazują, jak bardzo odczuwany jest jej brak w interfejsie API strumieni.
Marko Topolnik,

2
@Marko: takeWhile wydaje się bardziej, że byłaby to operacja dostarczająca przedmioty, a nie wykonująca akcję na każdym z nich. Z pewnością w LINQ w .NET byłoby kiepską formą stosowanie TakeWhile z działaniem niepożądanym.
Jon Skeet

9

Możesz użyć 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 będzie oferować obsługę operacji takeWhile na strumieniach.
pisaruk

6

Aby uzyskać maksymalną wydajność w operacjach równoległych, użyj findAny (), który jest podobny do findFirst ().

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

Jeśli jednak pożądany jest stabilny wynik, zamiast tego użyj findFirst ().

Zauważ również, że pasujące wzorce (anyMatch () / allMatch) zwrócą tylko wartość logiczną, nie otrzymasz dopasowanego obiektu.


6

Zaktualizuj do wersji Java 9+ z takeWhile:

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

3

Osiągnąłem coś takiego

  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

Możesz to osiągnąć, używając kombinacji peek (..) i anyMatch (..).

Na twoim przykładzie:

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

Lub po prostu napisz ogólną metodę util:

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

A następnie użyj go w następujący sposób:

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

0

A co z tym:

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

Gdzie BooleanWrapperjest klasa, którą należy wdrożyć, aby kontrolować przepływ.


3
A może AtomicBoolean?
Koekje,

1
Pomija to przetwarzanie obiektów, gdy !condition.ok()jednak nie zapobiega to forEach()zapętleniu wszystkich obiektów. Jeśli wolna część jest forEach()iteracją, a nie jej konsumentem (np. Pobiera obiekty z wolnego połączenia sieciowego), to podejście nie jest zbyt przydatne.
zakmck

Tak, masz rację, moja odpowiedź jest w tym przypadku dość błędna.
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;
});

Wykona operację tylko tam, gdzie znajdzie dopasowanie, a po znalezieniu dopasowania zatrzyma iterację.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.