Перерыв или возврат из Java 8 stream forEach?
при использовании внешние итерации на Iterable
мы используем:break
или return
из расширенного цикла for-each как:
for (SomeObject obj : someObjects) {
if (some_condition_met) {
break; // or return obj
}
}
как мы можем break
или return
С помощью внутренние итерации в Java 8 лямбда-выражения как:
someObjects.forEach(obj -> {
//what to do here?
})
11 ответов
Если вам это нужно, вы не должны использовать forEach
, но один из других методов, доступных в потоках; какой из них, зависит от вашей цели.
например, если целью этого цикла является поиск первого элемента, который соответствует некоторому предикату:
Optional<SomeObject> result =
someObjects.stream().filter(obj -> some_condition_met).findFirst();
(Примечание: это не будет повторять всю коллекцию, потому что потоки лениво оцениваются - он остановится на первом объекте, который соответствует условию).
если вы просто хотите знать, если есть элемент в коллекции, для которого условие true, можно использовать anyMatch
:
boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
этой is возможно Iterable.forEach()
(но не надежно с Stream.forEach()
). Решение не очень приятное, но это is возможно.
предупреждение: вы не должны использовать его для управления бизнес-логикой, но исключительно для обработки исключительной ситуации, которая возникает во время выполнения forEach()
. Например, ресурс внезапно перестает быть доступным, один из обрабатываемых объектов нарушает контракт (например, контракт говорит, что все элементы в потоке не должны быть null
но вдруг и неожиданно один из них null
) etc.
согласно документации для Iterable.forEach()
:
выполняет данное действие для каждого элемента
Iterable
до все элементы были обработаны или действие вызывает исключение... Исключения, действия передаются абоненту.
Итак, вы бросаете исключение, которое немедленно разорвет внутренний цикл.
код будет примерно такой - не могу сказать, что мне это нравится но это работает. Вы создаете свой собственный класс BreakException
которая расширяет 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
}
заметил, что try...catch
is не вокруг лямбда-выражения, а скорее вокруг всего 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
}
возврат в лямбде равен продолжению В for-each, но нет эквивалента перерыву. Вы можете просто сделать возврат, чтобы продолжить:
someObjects.forEach(obj -> {
if (some_condition_met) {
return;
}
})
ниже вы найдете решение, которое я использовал в проекте. Вместо forEach
просто использовать allMatch
:
someObjects.allMatch(obj -> {
return !some_condition_met;
});
или вам нужно использовать метод, который использует предикат, указывающий, следует ли продолжать (поэтому у него есть перерыв)или вам нужно выбросить исключение - что, конечно, очень уродливый подход.
так что вы могли бы написать forEachConditional
способ такой:
public static <T> void forEachConditional(Iterable<T> source,
Predicate<T> action) {
for (T item : source) {
if (!action.test(item)) {
break;
}
}
}
, а не Predicate<T>
, вы можете определить свой собственный функциональный интерфейс с тем же общим методом (что-то принимая T
и возврат bool
), но с именами, которые указывают на ожидание более четко - Predicate<T>
здесь не идеально.
вы можете использовать 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));
для максимальной производительности в параллельных операций использовать найти любое (), который похож на findFirst ().
Optional<SomeObject> result =
someObjects.stream().filter(obj -> some_condition_met).findAny();
однако, если требуется стабильный результат, вместо этого используйте findFirst ().
также обратите внимание, что совпадающие шаблоны (anyMatch()/allMatch) вернут только логическое значение, вы не получите сопоставленный объект.
Я достиг чего-то подобного
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;
}
вы можете достичь этого, используя сочетание peek(..) и anyMatch(..).
используя Ваш пример:
someObjects.stream().peek(obj -> {
<your code here>
}).anyMatch(obj -> !<some_condition_met>);
или просто напишите общий метод util:
public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
stream.peek(consumer).anyMatch(predicate.negate());
}
а затем используйте его, вот так:
streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
<your code here>
});
это нелегко сделать с помощью API, предоставленных в JDK. Но мы можем сделать это с помощью AbacusUtil. Вот пример кода:
Stream.of("a", "b", "c", "d", "e").peek(N::println) //
.forEach("", (r, e) -> r + e, (e, r) -> e.equals("c"));
// print: a b c
раскрытие информации: я разработчик AbacusUtil.
Как насчет этого:
окончательное условие BooleanWrapper = new BooleanWrapper ();
someObjects.forEach(obj -> {
if (condition.ok()) {
// YOUR CODE to control
condition.stop();
}
});
здесь BooleanWrapper
- это класс, который необходимо реализовать для управления потоком.