Java 8 Iterable 接口新增加的 forEach() 函数
公司的 web 应用基础框架几年前升级到 Java 8 之后, 很多开发人员很开心地使用起了很多 Java 8 提供的API新功能. 可是也遇到了不少问题. 其中一个便是使用非常广泛的 Iterable 的 forEach(Consumer<? super T> action) API.
在 Java 8 之前, 我们通常这样循环遍历一个可以 iterable 的对象:
public void oldWay() {
List<String> lines = Arrays.asList("test");
Iterator<String> iterator = lines.iterator();
while (iterator.hasNext()) {
//do something
System.out.println(iterator.next());
}
for (String line : lines) {
//do something
System.out.println(line);
}
}使用 Java 8 的 forEach() API 我们可以这么遍历对象
public void newWay() {
List<String> lines = Arrays.asList("test");
lines.forEach(new Consumer<String>() {
@Override
public void accept(String line) {
//do something
System.out.println(line);
}
});
// or use Lambda
lines.forEach(line -> {
//do something
System.out.println(line);
});
// or just println
lines.forEach(System.out::println);
}那么这个新的 API 到底有什么好处呢?
- 这个简洁的语法, 深受一些熟悉 JavaScript/Node.js 或者 其它脚本语言的开发者欢迎, 看上去简洁;
- 能够使用一些并行流的 API, 省去了自己维护 ExecutorService 的开销;
不过从实际应用来看, 遇到的问题更多.
- 无法抛出 checked exception, 从 Consumer 的 accept 方法可以看到, 它没有声明任何异常, 所以一旦里面执行的代码中需要跑出 checked exception, 直接无法使用这个 API;
- 无法中途退出. 如果我们执行一个10000次的循环, 如果其中某次出错, 我们不想执行后面的, 可是 forEach 无法做到; 无法像一般的 loop 一样使用 continue, return 等关键字达到的效果.
forEach 里面必须使用外层传入的 final 变量. 那么一旦是 final, 内层则无法直接赋值. 除非使用 container 类(如 一个对象的属性, map, list 等), 可以把值传出.
public void newWayMustFinal () { List<String> lines = Arrays.asList("test"); final Object finalTmp = "test"; Object tmp = null; lines.forEach(line -> { //do something if (finalTmp.equals(line)) { System.out.println(line); //tmp = line; // can not do this, as it is not final } }); }- 对于一般的 for loop, JVM 是可以进行优化的, 比如 for 循环有10000次, JVM 可以每次做100次, 这些 CPU 在某些指令( fetch, load, save) 等操作上可以实现流水线; forEach 暂时无法使用这些优化;
可读性差. 除了读 lambda 表达式感觉不直接, 另外读 stack 也不直接, 比如下面的 stack:
java.lang.Exception: Stack trace at java.lang.Thread.dumpStack(Thread.java:1336) at com.ilikecopy.basic.IterableForEach.lambda$0(IterableForEach.java:46) at java.util.Arrays$ArrayList.forEach(Arrays.java:3880) at com.ilikecopy.basic.IterableForEach.newWay(IterableForEach.java:43) at com.ilikecopy.basic.IterableForEach.main(IterableForEach.java:12)