【问题标题】:Break or return from Java 8 stream forEach?从 Java 8 流 forEach 中断或返回?
【发布时间】:2014-06-12 01:23:12
【问题描述】:

当在Iterable 上使用外部迭代时,我们使用增强的for-each 循环中的breakreturn

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

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

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

【问题讨论】:

  • 你不能。只需使用真正的for 声明即可。
  • 考虑另一种方法,你只想不执行代码,所以,forEach 中的一个简单的if 条件就可以解决问题。

标签: java foreach lambda java-8


【解决方案1】:

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

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

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

(注意:这不会迭代整个集合,因为流是延迟评估的 - 它会在第一个匹配条件的对象处停止)。

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

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

【讨论】:

  • 如果 finding 一个对象是目标,则此方法有效,但该目标通常由 findSomething 方法中的 return 语句实现。 break 通常与 take while 类操作相关联。
  • @MarkoTopolnik 是的,原始发布者没有给我们足够的信息来了解目标到底是什么;除了我提到的两种可能性之外,“暂时”是第三种可能性。 (有没有一种简单的方法可以对流进行“暂停”?)。
  • 当目标是正确实现取消行为时呢?当我们注意到用户请求取消时,我们能做的最好的事情是在 forEach lambda 中抛出运行时异常吗?
  • @HonzaZidek 已编辑,但关键不在于是否可能,而在于做事的正确方法是什么。您不应该为此尝试强制使用forEach;您应该改用另一种更合适的方法。
  • @Jesper 我同意你的观点,我写道我不喜欢“异常解决方案”。但是,您的措辞“forEach 不可能做到这一点”在技术上是不正确的。我也更喜欢您的解决方案,但是我可以想象我的答案中提供的解决方案更可取的用例:由于 real 异常而应该结束循环时。我同意您通常不应使用异常来控制流程。
【解决方案2】:

lambda 中的 return 等于 for-each 中的 continue,但不等同于 break。您可以返回以继续:

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

【讨论】:

  • 很好的惯用解决方案来满足一般要求。接受的答案推断了要求。
  • 换句话说,这并不是真正的问题“从 Java 8 流 forEach 中断或返回”
  • 这仍然会通过源流“拉”记录,如果您正在通过某种远程数据集进行分页,这很糟糕。
【解决方案3】:

对于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
}

【讨论】:

  • 我认为这是一种不好的做法,不应被视为解决问题的方法。这是危险的,因为它可能会误导初学者。根据 Effective Java 2nd Edition,第 9 章,第 57 项:“仅在异常情况下使用异常”。此外,“使用运行时异常来指示编程错误”。最后,我强烈鼓励任何考虑使用此解决方案的人研究@Jesper 解决方案。
  • @LouisF。我明确地说“我不能说我喜欢它,但它有效”。 OP问“如何摆脱forEach()”,这是一个答案。我完全同意这不应该用于控制业务逻辑。但是我可以想象一些有用的用例,比如在 forEach() 中间突然不可用的资源连接,使用异常不是坏习惯。为了清楚起见,我在答案中添加了一段。
  • 我认为这是一个很好的解决方案。在谷歌搜索“java exceptions”和其他几个词如“best practice”或“unchecked”等搜索后,我发现如何使用异常存在争议。我在我的代码中使用了这个解决方案,因为流正在执行一个需要几分钟的映射。我希望用户能够取消任务,所以我在每次计算开始时检查了标志“isUserCancelRequested”,并在为真时抛出异常。它很干净,异常代码被隔离到一小部分代码中,并且可以正常工作。
  • 请注意,Stream.forEach 确实 为将异常转发给调用者提供了同样强大的保证,因此对于 @987654337,不能保证以这种方式抛出异常@.
  • @Radiodef 这是一个有效的观点,谢谢。最初的帖子是关于Iterable.forEach(),但为了完整起见,我将您的观点添加到我的文字中。
【解决方案4】:

您可以在下面找到我在项目中使用的解决方案。而forEach 只需使用allMatch

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

【讨论】:

  • 我认为这正是我想要的。
【解决方案5】:

使用 takeWhile 更新 Java 9+:

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

【讨论】:

  • 从以前的答案,这需要 Java 9 。 OP专门询问了java 8
【解决方案6】:

要么你需要使用一个方法,该方法使用一个谓词来指示是否继续(所以它有中断)或者你需要抛出一个异常 - 这当然,这是一种非常丑陋的方法。

所以你可以像这样写一个forEachConditional 方法:

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

而不是Predicate&lt;T&gt;,您可能希望使用相同的通用方法(采用T 并返回bool)定义自己的功能接口,但名称更清楚地表明期望 - Predicate&lt;T&gt;这里不理想。

【讨论】:

  • 我建议在此处实际使用 Streams API和函数方法,如果无论如何使用 Java 8,则比创建此辅助方法更可取。
  • 这是经典的takeWhile 操作,而这个问题只是证明了它在 Streams API 中的缺失程度之一。
  • @Marko:takeWhile 感觉更像是一个产生项目的操作,而不是对每个项目执行操作。当然,在 .NET 中的 LINQ 中,将 TakeWhile 与具有副作用的操作一起使用会是一种糟糕的形式。
【解决方案7】:

你可以使用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));

【讨论】:

  • Java 9 将支持对流进行 takeWhile 操作。
【解决方案8】:

为了在并行操作中获得最大性能,请使用类似于 findFirst() 的 findAny()。

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

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

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

【讨论】:

    【解决方案9】:

    我已经通过这样的方式实现了

      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;
        }
    

    【讨论】:

      【解决方案10】:

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

      【讨论】:

      • anyMatch 不会停止第一次调用 peek。如果您只想在“some_condition_met”为真时在 peek lamda 中执行某些操作,则必须在 peek lamda 中添加 if 语句才能仅在“some_condition_met”为真时执行某些操作。
      【解决方案11】:
      public static void main(String[] args) {
          List<String> list = Arrays.asList("one", "two", "three", "seven", "nine");
          AtomicBoolean yes = new AtomicBoolean(true);
          list.stream().takeWhile(value -> yes.get()).forEach(value -> {
              System.out.println("prior cond" + value);
              if (value.equals("two")) {
                  System.out.println(value);
                  yes.set(false);
              }
      
          });
          //System.out.println("Hello World");
      }
      

      【讨论】:

      • 以上代码使用 java 9 的 takeWhile 方法和一个额外的变量来跟踪条件对我来说非常好。
      • 社区鼓励在代码中添加解释,而不是纯粹基于代码的答案(参见here)。
      • 您好,欢迎来到 SO!虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。请阅读tourHow do I write a good answer?
      【解决方案12】:

      这个呢:

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

      其中BooleanWrapper 是您必须实现以控制流程的类。

      【讨论】:

      • 还是 AtomicBoolean?
      • 这会在!condition.ok() 时跳过对象处理,但它不会阻止forEach() 循环遍历所有对象。如果慢的部分是forEach() 迭代而不是它的消费者(例如,它从慢速网络连接获取对象),那么这种方法不是很有用。
      • 是的,你是对的,在这种情况下我的回答是完全错误的。
      【解决方案13】:
      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;
      });
      

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

      【讨论】:

        【解决方案14】:

        我建议使用 anyMatch。示例:-

        return someObjects.stream().anyMatch(obj -> 
            some_condition_met;
        );
        

        您可以参考这篇文章来了解 anyMatch:- https://beginnersbook.com/2017/11/java-8-stream-anymatch-example/

        【讨论】:

          猜你喜欢
          • 2015-12-13
          • 1970-01-01
          • 1970-01-01
          • 2023-03-10
          • 1970-01-01
          • 2022-06-18
          • 1970-01-01
          • 2014-06-17
          相关资源
          最近更新 更多