从Java 8stream中断或返回forEach?
当对Iterable
使用外部迭代时 ,我们使用break
或者从增强的for-each循环return
:
for (SomeObject obj : someObjects) { if (some_condition_met) { break; // or return obj } }
我们如何使用Java 8 lambdaexpression式中的内部迭代来break
或return
:
someObjects.forEach(obj -> { //what to do here? })
如果你需要这个,你不应该使用forEach
,而应该使用其他方法之一。 哪一个取决于你的目标是什么。
例如,如果这个循环的目标是find匹配某个谓词的第一个元素:
Optional<SomeObject> result = someObjects.stream().filter(obj -> some_condition_met).findFirst();
(注意:这不会迭代整个集合,因为stream被懒惰地评估 – 它会停在匹配条件的第一个对象上)。
如果您只想知道集合中是否存在条件为true的元素,则可以使用anyMatch
:
boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
这forEach()
是可能的。 解决scheme不好,但它是可能的。
警告 :您不应该将其用于控制业务逻辑,而是纯粹用于处理执行forEach()
期间发生的exception情况。 如资源突然停止访问,其中一个被处理的对象违反合同(例如,合同中说,stream中的所有元素不能为null
但突然意外地其中一个为null
)等等。
根据Iterable.forEach()
的文档:
对
Iterable
每个元素执行给定的操作, 直到处理完所有元素,或者操作引发exception …将由操作引发的exception中继给调用者。
所以你抛出一个exception,将立即打破内部循环。
代码将是这样的 – 我不能说我喜欢它,但它的作品。 您创build了自己的类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
不在 lambdaexpression式周围,而是围绕整个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 }
下面你可以find我在一个项目中使用的解决scheme。 相反forEach
只使用allMatch
:
someObjects.allMatch(obj -> { return !some_condition_met; });
要么你需要使用一个方法,使用谓词表示是否继续(所以它有中断), 或者你需要抛出一个exception – 这当然是一个非常丑陋的方法。
所以你可以写这样的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>
在这里并不理想。
lambda中的返回等于for-each中的继续,但没有与break相等的效果。 你可以做一个返回继续:
someObjects.forEach(obj -> { if (some_condition_met) { return; } })
你可以使用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()的findAny ()。
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> });
用JDK提供的API来做这件事并不容易。 但是我们可以通过AbacusUtil来完成 。 这里是示例代码:
Stream.of("a", "b", "c", "d", "e").peek(N::println) // .forEach("", (r, e) -> r + e, (e, r) -> e.equals("c")); // print: abc
披露:我是AbacusUtil的开发者。