太棒了! 我们正在将代码库迁移到Java8。我们将用函数替换所有内容。 扔掉设计模式。 删除面向对象。 对! 我们走吧!

等一下

Java 8已经问世了一年多,而这种兴奋又回到了日常业务中。

baeldung.com从2015年5月开始执行的一项非代表性研究发现, 他们的读者中有38%已采用Java 8 。 在此之前,Typsafe在2014年末进行的一项研究声称,其用户中Java 8的采用率为27% 。

这对您的代码库意味着什么?

某些Java 7-> Java 8迁移重构是理所当然的。 例如,将Callable传递给ExecutorService

ExecutorService s = ...// Java 7 - meh...
Future<String> f = s.submit(new Callable<String>() {@Overridepublic String call() {return "Hello World";}}
);// Java 8 - of course!
Future<String> f = s.submit(() -> "Hello World");

匿名类样式实际上并没有在此添加任何值。

除了这些容易理解的话题之外,还有其他不太明显的话题。 例如,是否使用外部迭代器还是内部迭代器 。 另请参阅Neil Gafter于2007年发表的有关永恒主题的有趣读物: http ://gafter.blogspot.ch/2007/07/internal-versus-external-iterators.html

以下两个逻辑的结果相同

List<Integer> list = Arrays.asList(1, 2, 3);// Old school
for (Integer i : list)System.out.println(i);// "Modern"
list.forEach(System.out::println);

我主张“现代”方法应格外小心,即仅在您真正受益于内部功能迭代时(例如,通过Stream的map()flatMap()和其他操作链接一组操作map() ,才应格外小心。

与经典方法相比,这是“现代”方法的缺点的简短列表:

1.绩效–您将因此而蒙受损失

Angelika Langer在她的文章以及她在会议上发表的相关演讲中已经很好地总结了这个话题:

https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html

在许多情况下,性能并不重要,因此您不应该进行任何过早的优化-因此您可能会声称此参数本身并不是真正的参数。 但是在这种情况下,我会反驳这种态度,说Stream.forEach()与普通的for循环相比的开销如此之大以至于默认情况下使用它只会在您的所有计算机上堆积很多无用的CPU周期应用。 如果仅根据循环样式的选择来谈论将CPU消耗提高10%-20%,那么我们所做的根本就是错误的。 是的–各个循环无关紧要,但是可以避免整个系统的负担。

这是Angelika在普通循环上的基准测试结果,在装箱的整数列表中找到最大值:

ArrayList, for-loop : 6.55 ms
ArrayList, seq. stream: 8.33 ms

在其他情况下,当我们对原始数据类型执行相对简单的计算时,我们绝对应该退回到经典的for循环(最好是数组而不是集合)。

这是Angelika在普通循环上的基准测试结果,在原始整数数组中找到最大值:

int-array, for-loop : 0.36 ms
int-array, seq. stream: 5.35 ms

过早的优化效果不好,但是避免过早优化的货运教育更加糟糕。 重要的是要反思我们所处的环境,并在这种环境下做出正确的决定。 我们之前已经写过关于性能的博客,请参阅我们的文章《 Java十大简单性能优化》。

2.可读性–至少对于大多数人而言

我们是软件工程师。 我们将始终讨论我们的代码样式,就像它真的很重要一样。 例如,空格或花括号。

我们这样做的原因是因为软件维护很困难。 特别是别人编写的代码。 很久以前。 在切换到Java之前谁可能只写了C代码。

当然,在到目前为止的示例中,我们确实没有可读性问题,这两个版本可能是等效的:

List<Integer> list = Arrays.asList(1, 2, 3);// Old school
for (Integer i : list)System.out.println(i);// "Modern"
list.forEach(System.out::println);

但是这里发生了什么:

List<Integer> list = Arrays.asList(1, 2, 3);// Old school
for (Integer i : list)for (int j = 0; j < i; j++)System.out.println(i * j);// "Modern"
list.forEach(i -> {IntStream.range(0, i).forEach(j -> {System.out.println(i * j);});
});

事情开始变得更加有趣和异常。 我不是说“更糟”。 这是实践和习惯的问题。 并且没有黑/白问题的答案。 但是,如果其余代码库是必须的(可能是这样),则嵌套范围声明和forEach()调用以及lambda肯定是不寻常的,这会在团队中引起认知冲突 。

您可以构建一些示例,其中命令式方法比等效的功能式方法感觉更尴尬,如此处所示:

势在必行–功能分离pic.twitter.com/G2cC6iBkDJ

— Mario Fusco(@mariofusco) 2015年3月1日

但是在许多情况下,这是不正确的,并且编写功能上相对简单的命令相当的功能相当困难(同样,效率低下)。 可以在此博客的先前文章中看到一个示例: http : //blog.jooq.org/2015/09/09/how-to-use-java-8-functional-programming-to-generate-an-alphabetic -序列/

在那篇文章中,我们生成了一个字符序列:

A, B, ..., Z, AA, AB, ..., ZZ, AAA

…类似于MS Excel中的列:

命令式方法( 最初由Stack Overflow上的一个未命名用户使用 ):

import static java.lang.Math.*;private static String getString(int n) {char[] buf = new char[(int) floor(log(25 * (n + 1)) / log(26))];for (int i = buf.length - 1; i >= 0; i--) {n--;buf[i] = (char) ('A' + n % 26);n /= 26;}return new String(buf);
}

……在简洁的层次上可能胜过功能性的:

import java.util.List;import org.jooq.lambda.Seq;public class Test {public static void main(String[] args) {int max = 3;List<String> alphabet = Seq.rangeClosed('A', 'Z').map(Object::toString).toList();Seq.rangeClosed(1, max).flatMap(length ->Seq.rangeClosed(1, length - 1).foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)).map(t -> t.v1 + t.v2))).forEach(System.out::println);}
}

并且它已经在使用jOOλ来简化编写功能性Java的工作。

3.可维护性

让我们再次考虑前面的示例。 现在,我们不对值进行相乘,而是对它们进行除法。

List<Integer> list = Arrays.asList(1, 2, 3);// Old school
for (Integer i : list)for (int j = 0; j < i; j++)System.out.println(i / j);// "Modern"
list.forEach(i -> {IntStream.range(0, i).forEach(j -> {System.out.println(i / j);});
});

显然,这是自找麻烦,我们可以在异常堆栈跟踪中立即看到问题。

老套

Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test.main(Test.java:13)

现代

Exception in thread "main" java.lang.ArithmeticException: / by zeroat Test.lambda$1(Test.java:18)at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:557)at Test.lambda$0(Test.java:17)at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)at Test.main(Test.java:16)

哇。 我们只是…吗? 是。 这就是为什么我们首先在项目1中遇到性能问题的原因。 对于JVM和库而言,内部迭代只是要做更多的工作。 这是一个非常简单的用例,我们可以用AA, AB, .., ZZ系列的产品展示同样的东西。

从维护的角度来看,函数式编程风格比命令式编程要困难得多,尤其是当您在传统代码中盲目地将两种风格混合在一起时。

结论

这通常是一个关于函数式编程,关于声明式编程的博客。 我们喜欢lambda。 我们喜欢SQL。 结合起来,它们可以创造奇迹 。

但是,当您迁移到Java 8并考虑在代码中使用更多功能的样式时,请注意FP并不总是总会更好–出于各种原因。 实际上,它从来没有“更好”,它只是不同而已,它使我们对问题有不同的推理。

我们的Java开发人员将需要进行练习,并且对何时使用FP以及何时坚持使用OO /命令式有一个直观的了解。 通过适当的实践,将两者结合起来将有助于我们改进软件。

或者,用鲍伯叔叔的话来说:

底线就是这个。 当您知道OO编程是什么时,它就是好的。 当您知道函数式编程是什么时,它就是好的。 一旦了解了功能性OO编程,它也是不错的选择。

http://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html

翻译自: https://www.javacodegeeks.com/2015/12/3-reasons-shouldnt-replace-loops-stream-foreach.html

为什么不应该用Stream forEach替换for循环的3个原因相关推荐

  1. parallel循环java_Java 8 lambda stream forEach parallel 等循环与Java 7 for each 循环耗时测试...

    Java 8 里面的stream 有串行流和并行流之分. 说高级的stream就是那个并行流.下面是那个并行流的简单实现.只要是继承Collection类的都可以这么用. list.stream(). ...

  2. java stream foreach_Java 8 Lambda Stream forEach具有多个语句

    我仍在学习Lambda,请原谅我做错了什么 final Long tempId = 12345L; List updatedEntries = new LinkedList<>(); fo ...

  3. list stream().forEach

    遍历list的另一种写法 List<Integer> list= new ArrayList<>();         list.add(1);         list.ad ...

  4. .foreach()需要判断空吗_这次我们来聊聊 Stream#forEach 源码

    前言 上回说到了java.util.stream.Stream#forEach的三个问题: java.util.stream.Stream#forEach 是顺序消费吗? java.util.stre ...

  5. Java Stream forEach()和forEachOrdered()方法

    Java Stream forEach() and forEachOrdered() are terminal operations.Java Stream forEach()和forEachOrde ...

  6. stream.map 和 stream.foreach 的区别

    网上很多关于讲解这俩个区别的文章,但大多数要么不明不白,要么太复杂难理解.所以自己通俗的讲一下,毕竟不会太深奥,只是个人理解. 什么是 stream 流 我们在使用集合或数组对元素进行操作时往往会遇到 ...

  7. java8 循环map_Java8遍历Map的三种方式——for/stream/forEach

    最近写在基于Spring WebFlux项目遇到一个需求,希望将请求中的cookie/headers/params等信息获取,而获取后的数据结构都是MultiValueMap的数据结构,实质上可以看做 ...

  8. python lamda函数_python 用lambda函数替换for循环的方法

    场景如下: 现在有一个dataframe,其中一列为score,值从0-100, df: score 98 88 37 68 86 33 现在需要增加一列level,给这些分数分类,90分以上为A,6 ...

  9. java foreach和for循环区别_java相关:老生常谈foreach(增强for循环)和for的区别

    java相关:老生常谈foreach(增强for循环)和for的区别 发布于 2020-8-18| 复制链接 下面小妖就为大家带来一篇老生常谈foreach(增强for循环)和for的区别.小妖觉得挺 ...

最新文章

  1. C++14尝鲜:Generic Lambdas(泛型lambda)
  2. CSS border-image属性
  3. 长沙理工大学第十二届ACM大赛-重现赛C 安卓图案解锁 (模拟)
  4. 最近很火的百度MIP之 zblog改造
  5. tx2无法识别网络_Jetson TX2开篇--网络配置
  6. 特殊的forward_list操作
  7. 创建多个设备文件节点_使用DEVICE_ATTR实例分析
  8. python 小说分析_Python文章相关性分析---金庸武侠小说分析-2018.1.16
  9. 控制系统matlab仿真,控制系统MATLAB仿真作业
  10. DSA数字签名原理及python实现
  11. Apk资源文件混淆[微信开源方法]
  12. SVN E200030: There are unfinished transactions detected
  13. 虚拟化是什么,虚拟化主要分为哪几种?
  14. github访问不了怎么办
  15. 4070ti和3080性能差多少 rtx4070ti和rtx3080区别对比
  16. Autojs: 坚果云文本文件上传/下载
  17. 几种字符集与LPTSTR、LPCSTR、LPSTR、LPCTSTR、LPWSTR、LPCWSTR的意义
  18. 还在担心图片的版权吗?分享11个无版权、高清、免费图片素材网站给你!
  19. 【已解决】程序异常终止:Process finished with exit code -1073741819 (0xC0000005)
  20. 解决anaconda下Spyder的tensorflow不能用karas的原因,用anaconda安装最新的TensorFlow版本

热门文章

  1. 既然参与,那就做好,我相信我们是最棒的!!!
  2. Servlet使用适配器模式进行增删改查案例(EmpDaoImpl.java)
  3. 使用JDBCTemplate实现与Spring结合,方法公用 ——共用实现类(BaseImpl)
  4. SpringMVC 参数校验
  5. java 换行 运算符格式_Java代码样式运算符换行格式
  6. mqtt java_MQTT和Java入门
  7. java jigsaw_Java 9和Project Jigsaw如何破坏您的代码
  8. jboss fuse 教程_JBoss Fuse –一些鲜为人知的技巧
  9. .jdeveloper_在JDeveloper 12.1.3中为WebSocket使用Java API
  10. java.lang.Record:规范草案