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 到底有什么好处呢?

  1. 这个简洁的语法, 深受一些熟悉 JavaScript/Node.js 或者 其它脚本语言的开发者欢迎, 看上去简洁;
  2. 能够使用一些并行流的 API, 省去了自己维护 ExecutorService 的开销;

不过从实际应用来看, 遇到的问题更多.

  1. 无法抛出 checked exception, 从 Consumer 的 accept 方法可以看到, 它没有声明任何异常, 所以一旦里面执行的代码中需要跑出 checked exception, 直接无法使用这个 API;
  2. 无法中途退出. 如果我们执行一个10000次的循环, 如果其中某次出错, 我们不想执行后面的, 可是 forEach 无法做到; 无法像一般的 loop 一样使用 continue, return 等关键字达到的效果.
  3. 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
            }
        });
    }
  1. 对于一般的 for loop, JVM 是可以进行优化的, 比如 for 循环有10000次, JVM 可以每次做100次, 这些 CPU 在某些指令( fetch, load, save) 等操作上可以实现流水线; forEach 暂时无法使用这些优化;
  2. 可读性差. 除了读 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)

标签: none

添加新评论