从Java 8流中断还是返回forEach?


312

当在上使用外部迭代时,Iterable我们使用breakreturn通过增强的for-each循环进行以下操作:

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

我们如何break或在Java 8 lambda表达式中return使用内部迭代,例如:

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

6
你不能 只需使用真实的for陈述即可。
Boann 2014年


当然可能forEach()。解决方案不是很好,但是可能。请参阅下面的答案。
Honza Zidek 2015年

考虑另一种方法,您只想不执行代码,因此,将if内部的简单条件forEach发挥作用。
Thomas Decaux

Answers:


373

如果需要,则不应使用forEach,而应使用流中可用的其他方法之一。哪一个取决于您的目标是什么。

例如,如果此循环的目标是找到与某些谓词匹配的第一个元素:

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

(注意:这不会迭代整个集合,因为流是惰性计算的-它将在与条件匹配的第一个对象处停止)。

如果您只想知道集合中是否存在条件为真的元素,则可以使用anyMatch

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

7
如果找到一个对象是目标,则此方法有效,但是该目标通常由方法的return语句来实现findSomethingbreak通常与需要一段时间的操作有关。
Marko Topolnik 2014年

1
@MarkoTopolnik是的,原始海报没有给我们足够的信息来知道确切的目标是什么;除了我提到的两种方法外,“花点时间”是第三种可能性。(是否有一种简单的方法可以对流进行“花费时间”?)。
Jesper 2014年

3
当目标是正确实施取消行为时该怎么办?当我们注意到用户请求取消时,我们最好的办法就是在forEach lambda中抛出运行时异常?
user2163960 2015年

1
@HonzaZidek编辑,但重点不是是否可行,而是做事的正确方法。您不应该尝试为此强制使用forEach;您应该改用其他更合适的方法。
杰斯珀

1
@Jesper我同意你的看法,我写道我不喜欢“ Exception solution”。但是,您的措辞“这不可能用forEach”在技术上是错误的。我也更喜欢您的解决方案,但是我可以想象用例,在我的答案中提供的解决方案是更可取的:由于真正的异常而应该终止循环的时间。我同意您通常不应使用异常来控制流程。
Honza Zidek '16

53

可能的Iterable.forEach()(但使用不能可靠Stream.forEach())。解决方案不是很好,但是可能。

警告:您不应将其用于控制​​业务逻辑,而应仅用于处理在执行期间发生的异常情况forEach()。例如资源突然停止访问,处理的对象之一违反了契约(例如,契约说流中的所有元素一定不是,null但突然而意外地是其中的一个null),等等。

根据文档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不是周围的lambda表达式,而是围绕整个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
我认为这是一种不良做法,不应视为解决问题的方法。这很危险,因为它可能会对初学者产生误导。根据有效Java第二版,第9章,项目57:“仅在特殊情况下使用例外”。此外,“使用运行时异常指示编程错误”。最后,我强烈建议任何考虑此解决方案的人使用@Jesper解决方案。
路易F.16年

15
@LouisF。我明确地说:“我不能说我喜欢它,但它能奏效”。OP询问“如何脱离forEach()”,这是一个答案。我完全同意,不应将其用于控制​​业务逻辑。但是,我可以想象一些有用的用例,例如在forEach()中间突然不可用的资源连接,使用异常并不是一种不好的做法。我在回答中添加了一段,以便清楚。
Honza Zidek '16

1
我认为这是一个很好的解决方案。在Google搜索“ java异常”并用其他词(例如“最佳实践”或“未选中”等)进行其他搜索之后,我发现有关如何使用异常的争议。我在代码中使用了此解决方案,因为该流执行的地图需要几分钟的时间。我希望用户能够取消任务,因此我在每次计算的开始都检查了“ isUserCancelRequested”标志,并在true时引发了异常。这很干净,异常代码被隔离在代码的一小部分,并且可以正常工作。
杰森

2
请注意,对于将中继转发给调用方的异常,Stream.forEach没有提供同样的有力保证,因此,不能保证抛出异常会以这种方式工作Stream.forEach
Radiodef

1
@Radiodef这是一个正确的观点,谢谢。最初的帖子大约是Iterable.forEach(),但是我只是为了完整性而将您的观点添加到我的文字中。
Honza Zidek '18年

43

lambda的返回等于for-for的继续,但没有中断。您可以返回以继续:

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

2
不错的惯用解决方案,可以满足一般要求。接受的答案推断要求。
davidxxx'3

6
换句话说,这不是“实际中断Java 8流或从Java 8流返回”
JaroslavZáruba18年

1
但是,这仍将“拉”记录通过源流,如果您正在通过某种远程数据集进行分页,则这是不好的。
阿德里安·贝克


11

要么需要使用一种使用谓词来指示是否继续运行的方法(否则它就会有中断),要么您需要抛出一个异常-当然,这是非常丑陋的方法。

因此,您可以编写这样的forEachConditional方法:

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

而不是Predicate<T>,您可能希望使用相同的常规方法(用a T并返回a bool)定义自己的功能接口,但使用名称更清楚地表示期望值- Predicate<T>在此并不理想。


1
我建议,如果无论如何都使用Java 8 ,实际上在这里使用Streams API 和一种功能方法比创建此辅助方法更可取。
skiwi 2014年

3
这是经典的takeWhile操作,这个问题只是表明在Streams API中多少感觉不到它的问题之一。
Marko Topolnik 2014年

2
@Marko:takeWile感觉更像是产生项目的操作,而不是对每个项目执行操作。当然,在.NET的LINQ中,将TakeWhile与具有副作用的动作一起使用将是一种糟糕的形式。
乔恩·斯基特

9

您可以使用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将为流上的takeWhile操作提供支持。
pisaruk

6

为了在并行操作中获得最佳性能,请使用findAny(),它与findFirst()相似。

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

但是,如果需要稳定的结果,请改用findFirst()。

还要注意,匹配模式(anyMatch()/ allMatch)将仅返回布尔值,而不会得到匹配的对象。


6

使用Java 9+更新takeWhile

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

3

我已经取得了这样的成就

  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

您可以混合使用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>
});

0

这个如何:

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

BooleanWrapper您必须在哪里实现控制流的类。


3
还是AtomicBoolean?
Koekje

1
当时!condition.ok(),这会跳过对象处理,但是无论如何,它不会阻止forEach()循环所有对象。如果最慢的部分是forEach()迭代而不是它的使用者(例如,它是从慢速的网络连接获取对象),那么这种方法不是很有用。
zakmck

是的,您是对的,在这种情况下,我的回答是非常错误的。
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;
});

它只会在找到匹配项的地方进行操作,找到匹配项后会停止迭代。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.