今天又是开心撸码的一天!但是,在使用集合的 forEach() 方法时候,一个 Bug 刺醒了我!
描述:通过循环集合,当匹配到一个对象时候,终止循环!
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 ,先执行一遍再说。
// 执行结果
A B C D F G
通过执行结果发现,没有输出 E,而 E 后面的 F、G 竟然也输出了!
难道 Java8 出 Bug了?break 失效!return 等同于 continue?
我们知道,在普通for循环里面,想要提前终止循环体使用 break;
结束本轮循环,进行下一轮循环使用 continue;
另外,在普通for里,如果使用 return ; 不仅强制结束 for 循环体,还会提前结束包含这个循环体的整个方法。
而在 Java8 中的 forEach() 中, "break " 或 "continue" 是不被允许使用的,而 "return" 的意思也不是原来代表的含义了。
我们来看看源码:
/**
* @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 循环
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");
}
}
// 执行结果
A B C D
A B C D
方案二:在 forEach() 中抛出异常
我们知道,要想结束一个方法的执行,正常的逻辑是:使用 return;但是,在实际运行中,往往有很多突发情况导致代码提前终止,比如:异常。那么我们也可以通过抛出假异常的方式来达到终止 forEach() 方法的目的。
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("这里执行不到!");
}
但是值得注意的是,这种抛出异常来终止循环,那么后续的代码也是无法继续执行的。
// 执行结果
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...
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("这里可以执行!");
}
// 执行结果
A B C D
这里可以执行!
这样,相对就完美了。
这里,需要注意的一点是:要确保你 forEach() 方法体内不能有其它代码可能会抛出的异常;否则,当真正该因异常导致代码终止的时候,因为咱们手动捕获了并且没做任何处理,岂不是搬起石头砸自己的脚吗?
但是,这样的代码看起来也是不太美观的,所以总的来说,如果有终止循环的需求,还是老老实实的写 for 循环比较靠谱,Lambda 在这里并不太实用!
forEach() 就是一个方法,参数是函数式接口,并不是一个循环体,不是设计为可以用 break 以及 continue 来中止的操作。
END
@一个正经的程序员