前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >震惊!Java8 出 Bug了?break 失效!return 等同于 continue?

震惊!Java8 出 Bug了?break 失效!return 等同于 continue?

作者头像
一个正经的程序员
发布2022-07-20 18:10:49
5010
发布2022-07-20 18:10:49
举报
文章被收录于专栏:一个正经的程序员

情景再现

今天又是开心撸码的一天!但是,在使用集合的 forEach() 方法时候,一个 Bug 刺醒了我!

描述:通过循环集合,当匹配到一个对象时候,终止循环!

代码语言:javascript
复制
public static void main(String[] args) {
    List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
    list.forEach(str -> {
      if (Objects.equals(str, "E")) {
        // 这里竟然不能使用 break/continue;
        return;
      }
      System.err.print(str + "\t");
    });
}

然后发现,在循环体内不能使用 break 来终止循环,然后又尝试 continue 也不能使用,无奈尝试了一下 return,神奇,只能使用 return ,先执行一遍再说。

代码语言:javascript
复制
// 执行结果

A  B  C  D  F  G  

通过执行结果发现,没有输出 E,而 E 后面的 FG 竟然也输出了!

难道 Java8 出 Bug了?break 失效!return 等同于 continue?

原因分析

我们知道,在普通for循环里面,想要提前终止循环体使用 break;

结束本轮循环,进行下一轮循环使用 continue;

另外,在普通for里,如果使用 return ; 不仅强制结束 for 循环体,还会提前结束包含这个循环体的整个方法。

而在 Java8 中的 forEach() 中, "break " 或 "continue" 是不被允许使用的,而 "return" 的意思也不是原来代表的含义了。

我们来看看源码:

代码语言:javascript
复制
/**
 * @implSpec
 * <p>The default implementation behaves as if:
 * <pre>{@code
 *     for (T t : this)
 *         action.accept(t);
 * }</pre>
 *
 * @param action The action to be performed for each element
 * @throws NullPointerException if the specified action is null
 * @since 1.8
 */
default void forEach(Consumer<? super T> action) {
      Objects.requireNonNull(action);
      for (T t : this) {
          action.accept(t);
      }
}

通过源码来看, forEach() 说到底就是一个方法,而不是作为关键字的循环体,那么结束一个方法的执行用什么?当然还是 return

由此可以发现,forEach() 只要你使用它,就一定会遍历完所有元素。

解决方案

方案一:使用原始的 for 循环或者增强 for 循环

代码语言:javascript
复制
public static void main(String[] args) {
    List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
    // 1
    for (int i = 0; i < list.size(); i++) {
      if (Objects.equals(list.get(i), "E")) {
        break;
      }
      System.err.print(list.get(i) + "\t");
    }
    System.err.println();
    // 2
    for (String str : list) {
      if (Objects.equals(str, "E")) {
        break;
      }
      System.err.print(str + "\t");
    }

}
代码语言:javascript
复制
// 执行结果
A  B  C  D  
A  B  C  D

方案二:在 forEach() 中抛出异常

我们知道,要想结束一个方法的执行,正常的逻辑是:使用 return;但是,在实际运行中,往往有很多突发情况导致代码提前终止,比如:异常。那么我们也可以通过抛出假异常的方式来达到终止 forEach() 方法的目的。

代码语言:javascript
复制
public static void main(String[] args) {
    List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
    list.forEach(str -> {
      if (Objects.equals(str, "E")) {
        throw new RuntimeException("终止 for 循环");
      }
      System.err.print(str + "\t");
    });
    System.err.println("这里执行不到!");
}

但是值得注意的是,这种抛出异常来终止循环,那么后续的代码也是无法继续执行的。

代码语言:javascript
复制
// 执行结果
A  B  C  D  
Exception in thread "main" java.lang.RuntimeException: 终止 for 循环
  at llc.iob.iobots.test.LouisTest.lambda$main$0(LouisTest.java:76)
  at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)
  at llc.iob.iobots.test.LouisTest.main(LouisTest.java:74)

如果觉得不友好的话,还可以继续包装一层 try...catch...

代码语言:javascript
复制
public static void main(String[] args) {
    List<String> list = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
    try {
      list.forEach(str -> {
        if (Objects.equals(str, "E")) {
          throw new RuntimeException("终止 for 循环");
        }
        System.err.print(str + "\t");
      });
    } catch (Exception e) {}
    System.err.println("这里可以执行!");
  }
代码语言:javascript
复制
// 执行结果
A  B  C  D  
这里可以执行!

这样,相对就完美了。

这里,需要注意的一点是:要确保你 forEach() 方法体内不能有其它代码可能会抛出的异常;否则,当真正该因异常导致代码终止的时候,因为咱们手动捕获了并且没做任何处理,岂不是搬起石头砸自己的脚吗?

但是,这样的代码看起来也是不太美观的,所以总的来说,如果有终止循环的需求,还是老老实实的写 for 循环比较靠谱,Lambda 在这里并不太实用!

结论

forEach() 就是一个方法,参数是函数式接口,并不是一个循环体,不是设计为可以用 break 以及 continue 来中止的操作。

END

@一个正经的程序员

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个正经的程序员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 情景再现
  • 原因分析
  • 解决方案
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档