找回密码
 会员注册
查看: 37|回复: 0

详细分析Java中的list.foreach()和list.stream().foreach()

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
64074
发表于 2024-9-13 11:43:27 | 显示全部楼层 |阅读模式
目录前言1.基本知识2.差异之处2.1执行顺序2.2串行并行2.3复杂数据处理2.4CRUD集合2.5迭代器3.总结4.彩蛋前言典故来源于项目中使用了两种方式的foreach,后面尝试体验下有何区别!先看代码示例:使用List的forEach:importjava.util.Arrays;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//使用List的forEachdata.forEach(element->{System.out.println("Element:"+element);});}}12345678910111213使用StreamAPI的forEach:importjava.util.Arrays;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//使用StreamAPI的forEachdata.stream().forEach(element->{System.out.println("Element:"+element);});}}12345678910111213两者输出结果都为如下:既然两个都输出差不多的结果,但两者还是稍微有些小区别,具体看下文!1.基本知识forEach()是List接口的一部分,用于对列表中的每个元素执行给定的操作。与StreamAPI的forEach不同,list的forEach不支持并行处理,因此它在处理大量数据时可能不如StreamAPI高效。适用于简单的迭代和操作,不需要复杂的流处理。stream().forEach()是StreamAPI的一部分,用于对流中的每个元素执行给定的操作。使用StreamAPI的forEach允许进行更多的操作,例如过滤、映射等,因为你可以在流上链式调用其他方法。StreamAPI提供了更多的灵活性和功能,适用于处理大量数据或进行复杂的操作。两者更多的区别可看下表:代码块概念作用差异之处适用场景list.foreach()List接口中的方法,用于对列表中的每个元素执行给定的操作1.通过forEach(),可以对列表中的每个元素进行遍历,并在每个元素上执行指定的操作。2.主要用于简单的迭代和操作,适用于不需要复杂流处理的场景。1.并行处理:List的forEach不支持并行处理,因此在处理大量数据时可能不如StreamAPI高效。StreamAPI的并行处理机制可以更好地利用多核处理器的性能。2.功能限制:List的forEach主要用于简单的迭代操作,而没有提供像StreamAPI那样的丰富中间操作和终端操作,限制了其在复杂数据处理场景中的应用。适用于简单的遍历和操作,例如对列表中的元素进行打印、计算简单统计量等。不适用于需要复杂处理逻辑的情况。stream().foreach()Java8引入的StreamAPI中的方法,用于对流中的每个元素执行给定的操作。1.通过forEach(),可以对流中的每个元素进行遍历,并在每个元素上执行指定的操作。2.StreamAPI提供了一种更为函数式的方式来处理数据,允许链式调用其他方法,如过滤、映射、排序等。1.链式调用:使用StreamAPI的forEach允许在流上进行链式调用其他方法,使得可以进行更多的操作。这种链式调用是函数式编程的一种特征,使代码更为灵活和表达性强。2.功能丰富:StreamAPI提供了更多的中间操作和终端操作,例如map、filter、reduce等,这些操作可以组合使用,实现更复杂的数据处理逻辑。适用于处理大量数据或进行复杂的操作,例如对数据进行变换、过滤、聚合等。StreamAPI的并行处理机制也使得在多核处理器上能够更高效地处理大规模数据。总结:2.差异之处此处的差异之处主要讲解stream().foreach()比list.foreach()多的点2.1执行顺序stream流中如果结合parallelStream(),会有不一样的特性!importjava.util.Arrays;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//使用StreamAPI中的parallelStreamforEachdata.parallelStream().forEach(element->{System.out.println("Element:"+element);});}}12345678910111213截图如下:在使用parallelStream()进行并行处理时,元素的处理顺序可能不会按照原始列表中的顺序输出。并行流会将数据分成多个块,然后并行处理这些块,最后将结果合并。因此,并行处理的结果可能在不同的块之间交错,导致输出的顺序不再按照原始列表的顺序。想要保持顺序,可以使用forEachOrdered()方法,它会保证按照流的遍历顺序输出结果(并行流的情况下保持元素的遍历顺序,但可能会牺牲一些并行处理的性能优势)。示例如下:importjava.util.Arrays;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//使用StreamAPI的parallelStream().forEachOrdereddata.parallelStream().forEachOrdered(element->{System.out.println("Element:"+element);});}}12345678910111213截图如下:2.2串行并行对应上章节继续科普parallelStream(),一般来说并行的输出速度比较快,用于对顺序输出不讲究,但是大量的数据处理可以用这个!对于少量的数据来说,效果不明显,甚至会高于list.foreach(),所以根据情况而来,一般数据处理多,可以应用parallelStream()importjava.util.ArrayList;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listnumbers=newArrayList();for(inti=1;i{//模拟一些耗时的操作Math.pow(element,2);});longendTime=System.currentTimeMillis();System.out.println("TimetakenwithStream.forEach(parallel):"+(endTime-startTime)+"ms");}}12345678910111213141516171819202122截图如下所示:原本以为使用list.foreach(),时间会更长,反而时间更短:importjava.util.ArrayList;importjava.util.List;publicclassDemo{publicstaticvoidmain(String[]args){Listnumbers=newArrayList();for(inti=1;i{//模拟一些耗时的操作Math.pow(element,2);});longendTime=System.currentTimeMillis();System.out.println("TimetakenwithList.forEach(sequential):"+(endTime-startTime)+"ms");}}12345678910111213141516171819202122截图如下:可能是由于数据规模较小、并行处理带来的额外开销以及模拟的耗时操作较短,导致并行流的性能没有得到有效提升。并行处理的优势在于处理大规模数据时更为显著。2.3复杂数据处理StreamAPI提供了丰富的中间操作和终端操作,使得能够进行更复杂的数据处理。下面举例说明filter()、map()、reduce()这几个常用的操作方法。filter()用于对流中的元素进行过滤,只保留满足某个条件的元素:importjava.util.Arrays;importjava.util.List;importjava.util.stream.Collectors;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//过滤出长度大于3的元素ListfilteredList=data.stream().filter(element->element.length()>3).collect(Collectors.toList());System.out.println("FilteredList:"+filteredList);}}12345678910111213141516截图如下:map()用于对流中的每个元素进行映射转换,生成一个新的流:importjava.util.Arrays;importjava.util.List;importjava.util.stream.Collectors;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three","Four","Five");//将每个元素转换为大写ListuppercasedList=data.stream().map(String::toUpperCase).collect(Collectors.toList());System.out.println("UppercasedList:"+uppercasedList);}}12345678910111213141516截图如下:reduce()用于将流中的元素逐个进行操作,最终得到一个结果importjava.util.Arrays;importjava.util.List;importjava.util.Optional;publicclassDemo{publicstaticvoidmain(String[]args){Listnumbers=Arrays.asList(1,2,3,4,5);//对所有元素求和Optionalsum=numbers.stream().reduce((x,y)->x+y);sum.ifPresent(result->System.out.println("Sum:"+result));}}123456789101112131415使用Optional主要是为了处理可能没有元素的情况,避免返回null。考虑到numbers可能为空,如果直接使用reduce((x,y)->x+y),当numbers为空时,会得到null,而这可能导致空指针异常。截图如下:拓展1对于上述代码中,都有使用到Collectors.toList()Collectors.toList()是Collectors工具类提供的一个静态方法,它用于将流中的元素收集到一个List集合中。在StreamAPI中,对流进行一系列的操作后,通常会希望将结果收集到一个集合中,以便后续的操作或输出。在实际应用中,还可以使用其他的Collectors方法,如toSet()、toMap()等,以便根据需求将元素收集到不同的集合类型中。拓展2对于上述reduce中,使用到了Optional类Optional是Java8引入的一个类,用于处理可能为null的值。它的设计目的是避免使用null,减少空指针异常的发生,并提供更安全、清晰的代码。在上述代码中,通过使用Optional,可以更安全地处理可能为空的情况。如果numbers为空,reduce操作的结果将是Optional.empty(),而不是null。importjava.util.Arrays;importjava.util.List;importjava.util.Optional;publicclassReduceWithOptionalExample{publicstaticvoidmain(String[]args){Listnumbers=Arrays.asList(1,2,3,4,5);//使用Optional处理可能为空的情况Optionalsum=numbers.stream().reduce((x,y)->x+y);//判断Optional是否包含值if(sum.isPresent()){System.out.println("Sum:"+sum.get());}else{System.out.println("Thelistisempty.");}}}12345678910111213141516171819202.4CRUD集合当使用list.foreach()或stream().foreach()进行遍历操作时,如果在遍历过程中对集合进行了增、删、改的操作,可能会导致ConcurrentModificationException异常。一、list.foreach()的增删:增加数据的Demo代码:importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=newArrayList();data.add("One");data.add("Two");data.add("Three");//使用List.forEach遍历,并在遍历过程中增加元素data.forEach(element->{System.out.println("Element:"+element);if(element.equals("Two")){data.add("Four");//在遍历过程中增加元素}});}}123456789101112131415161718以及删除数据Demo:importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=newArrayList();data.add("One");data.add("Two");data.add("Three");//使用List.forEach遍历,并在遍历过程中删除元素data.forEach(element->{System.out.println("Element:"+element);if(element.equals("Two")){data.remove(element);//在遍历过程中删除元素,可能导致异常}});}}123456789101112131415161718最终的结果截图如下:二、list.stream().foreach()的增删:增加数据的Demo代码:importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=newArrayList();data.add("One");data.add("Two");data.add("Three");//使用Stream.forEach遍历,并在遍历过程中增加元素data.stream().forEach(element->{System.out.println("Element:"+element);if(element.equals("Two")){data.add("Four");//在遍历过程中增加元素}});}}123456789101112131415161718截图如下:以及删除数据Demo:importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=newArrayList();data.add("One");data.add("Two");data.add("Three");//使用Stream.forEach遍历,并在遍历过程中删除元素data.stream().forEach(element->{System.out.println("Element:"+element);if(element.equals("Two")){data.remove(element);//在遍历过程中删除元素,可能导致异常}});}}123456789101112131415161718截图如下:通过上述四个程序的执行结果,可以得到什么体会呢???stream().foreach()在操作集合的CRUD的时候,执行错误之后,还是会迭代整个列表,才看到异常这也证明stream().foreach()为并行执行处理数据,而不是串行那有办法解决这种异常的情况么,又能对集合进行CRUD!(答案是有的,可看如下正文)2.5迭代器通过上述的阅读,急需需要一个对集合的CRUD做一个安全性的迭代!于是有了如下的解决方式:importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=newArrayList();data.add("One");data.add("Two");data.add("Three");//使用迭代器遍历,并在条件满足时删除元素Iteratoriterator=data.iterator();while(iterator.hasNext()){Stringelement=iterator.next();System.out.println("Element:"+element);if(element.equals("Two")){iterator.remove();//尝试在不支持结构性修改的列表上进行删除操作,抛出异常}}System.out.println("ModifiedList:"+data);}}12345678910111213141516171819202122截图如下:3.总结总结起来,对于上面的代码学习,主要涉及了两种遍历集合的方式:List.forEach()和List.stream().forEach()。下面对这两种方式的区别进行总结:List.forEach()方法:遍历是在当前线程中按顺序执行的,对集合元素的操作是同步的。适用于简单的、顺序执行的遍历操作。不支持并行操作,不保证源数据的顺序。List.stream().forEach()方法:可以利用并行流进行多线程处理,提高遍历效率,不保证源数据的顺序。适用于更复杂的、并行处理的遍历操作,可以配合Stream的其他操作进行更灵活的数据处理。4.彩蛋对于上述中的代码,有一个需要注意的点:不管是list.foreach()还是list.stream().foreach(),集合的CRUD都会出现会爆:Exceptioninthread"main"java.lang.UnsupportedOperationException的错误,举一个例子。importjava.util.*;publicclassDemo{publicstaticvoidmain(String[]args){Listdata=Arrays.asList("One","Two","Three");//使用List.forEach遍历,并在遍历过程中删除元素data.forEach(element->{System.out.println("Element:"+element);if(element.equals("Two")){data.remove(element);//在遍历过程中删除元素,可能导致异常}});}}123456789101112131415截图如下:主要的原因在于:Arrays.asList("One","Two","Three","Four","Five")创建的列表是由数组支持的固定大小的列表。这意味着该列表不支持结构性修改操作(如添加、删除),并且会在尝试进行这些操作时抛出UnsupportedOperationException异常。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 11:12 , Processed in 0.453859 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表