forEachLambdaMaxInteger()——这个用例有点混乱。可能是因为 Java 8 的 forEach 特性有一个很烦人的东西:只能使用 final 变量,所以我们创建一个 final 包装类来解决该问题,这样我们就能访问到更新后的最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
public int forEachLambdaMaxInteger() { final Wrapper wrapper = new Wrapper(); wrapper.inner = Integer.MIN_VALUE; integers.forEach(i -> helper(i, wrapper)); return wrapper.inner.intValue(); } public static class Wrapper { public Integer inner; } private int helper(int i, Wrapper wrapper) { wrapper.inner = Math.max(i, wrapper.inner); return wrapper.inner; }
顺便提一下,如果要讨论 forEach,我们提供了一些有趣的关于它的缺点的见解,答案参见 。
streamMaxInteger()——使用 Java 8 的流遍历列表:
1 2 3 4
public int streamMaxInteger() { Optional max = integers.stream().reduce(Integer::max); return max.get(); }
优化后的基准测试
根据这篇文章的反馈,我们创建另一个版本的基准测试。源代码的不同之处可以在这里查看。下面是测试结果:
列表不再用 volatile 修饰。
新方法 forMax2 删除对成员变量的访问。
删除 forEachLambda 中的冗余 helper 函数。现在 lambda 表达式作为一个值赋给变量。可读性有所降低,但是速度更快。
消除自动装箱。如果你在 Eclipse 中打开项目的自动装箱警告,旧的代码会有 15 处警告。
优化流代码,在 reduce 前先使用 mapToInt。
非常感谢 Patrick Reinhart, Richard Warburton, Yan Bonnel, Sergey Kuksenko, Jeff Maxwell, Henrik Gustafsson 以及每个 Twitter 上评论的人,感谢你们的贡献。
我们使用 JMH(Java Microbenchmarking Harness) 执行基准测试。如果想知道怎么将其应用在你自己的项目中,可以参考这篇文章,我们通过一个自己写的实例来演示 JMH 的主要特性。
基础测试的配置包含 2 个JVM、5 次预热迭代和 5 次测量迭代。该测试运行在 c3.xlarge Amazon EC2 实例上(CPU:4 核,内存:7.5G,存储:2 x 40 GB SSD),采用 Java 8u66 和 JMH 1.11.2。所有的源代码都在 GitHub 上,你可以在这里看到原始的输出结果。
顺便做一下免责申明:基准测试往往不是完全可信的,也很难保证绝对正确。虽然我们试图以最准确的方式来运行,但仍然建议接受结果时抱有怀疑的态度。
最后的思考开始使用 Java 8 的第一件事情是在实践中使用 lambda 表达式和流。但是请记住:它确实非常好,好到可能会让你上瘾!但是,我们也看到了,使用传统迭代器和 for-each 循环的 Java 编程风格比 Java 8 中的新方式性能高很多。
当然,这也不是绝对的。但这确实是一个相当常见的例子,它显示可能会有大约 5 倍的性能差距。如果这影响到系统的核心功能或成为系统一个新的瓶颈,那就相当可怕了。