涉及的文章链接:ArrayList 循环Remove遇到的坑
一、想总结本篇博客的原因
在日常开发中,需要对集合数据进行或多或少的赋值修改,那么循环赋值或者做一些处理就是最常见的一种操作了,但是用习惯了stream流,所以在循环的过程中就很习惯的使用stream.forEach的方式,可是编码规范却提示用.forEach代替,这是为何呢?
二、stream.forEach 和 .forEach都所处何处?
1).forEach
去定位代码位置,就会发现其在java.lang包下,list.forEach则属于最基本的操作了。因为是基本操作,所以它并不支持并行处理,因此它在处理大量数据时可能不如Stream API高效。
2)stream.forEach
所属java.util.stream下,是Stream API的一部分,用于对流中的每个元素执行给定的操作。使用Stream API的 forEach 允许进行更多的操作,例如过滤、映射等。Stream API提供了更多的灵活性和功能,适用于处理大量数据或进行复杂的操作。
三、stream.forEach 和 .forEach 代码示例
java
public static void main(String[] args) {
List<String> list= Arrays.asList("Sunday","Monday","TuesDay","WednesDay","ThursDay","Friday","Saturday");
list.forEach(d-> System.out.println("value:"+d));
list.stream().forEach(f-> System.out.println("value1:"+f));
list.stream().filter(d->d.length()>3).forEach(s-> System.out.println("value3:"+s));
}
---总结差异使用
如上demo,当时普通的循环输出的时候,idea会提示我们使用.forEach来替代.stream.forEach,因为仅仅是简单的输出或者赋值,再无其他的操纵;但是当需要对集合做一些额外处理,比如筛选的时候,我们仅仅用.forEach则满足不了需求了,需要先提前将list进行筛选,再进行forEach操作;
四、 parallelStream.forEach
1)parallelStream
表面含义理解也能晓得相比stream而言,他是一种并行的流。它通过 Fork/Join 框架来拆分任务,加速流的处理过程。Fork/Join 的框架是通过把一个大任务不断 fork 成许多子任务,然后多线程执行这些子任务,最后再 Join 这些子任务得到最终结果。
2)parallelStream.forEach demo
① 性能方向
java
public static void main(String[] args) {
List<Integer> numberList=new ArrayList<>();
for (int i=0;i<1000000;i++){
numberList.add(i);
}
Long startTime=System.currentTimeMillis();
List<Integer> parallelList=new ArrayList<>();
numberList.parallelStream().forEach(d->{
double powValue= Math.pow(d,3);
});
Long endTime=System.currentTimeMillis();
System.out.println("parallel:"+(endTime-startTime));
}
②准确性方向
java
public static void main(String[] args) {
List<Integer> numberList=new ArrayList<>();
for (int i=0;i<10000;i++){
numberList.add(i);
}
System.out.println("size="+numberList.size());
List<Integer> parallelList=new ArrayList<>();
numberList.parallelStream().forEach(parallelList::add);
System.out.println("size parallel="+parallelList.size());
}
看如上代码,执行结果会是什么呢?是size和size parallel都是10000,还是其他?可以自行测试一下,我测试的结果是报错了。
java
size=10000
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4605
at java.util.ArrayList.add(ArrayList.java:465)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:401)
at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:650)
at com.meta.image.user.service.impl.AchievementServiceImpl.main(AchievementServiceImpl.java:1159)
注:报错分析详情下篇博客。
五、stream.forEach 和 .forEach的CRUD操作
1)java.lang.UnsupportedOperationException 顺带提一嘴
java
public static void main(String[] args) {
List<String> list= Arrays.asList("Sunday","Monday","TuesDay","WednesDay","ThursDay","Friday","Saturday");
list.forEach(d->{
System.out.println("value="+d);
if (d.equals("ThursDay")){
list.add("other");
}
});
}
java
public static void main(String[] args) {
List<String> list= Arrays.asList("Sunday","Monday","TuesDay","WednesDay","ThursDay","Friday","Saturday");
list.stream().forEach(d->{
System.out.println("value="+d);
if (d.equals("ThursDay")){
list.add("other");
}
});
}
结果均是一样的
bash
value=Sunday
value=Monday
value=TuesDay
value=WednesDay
value=ThursDay
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.meta.image.user.service.impl.AchievementServiceImpl.lambda$main$10(AchievementServiceImpl.java:1154)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647)
at com.meta.image.user.service.impl.AchievementServiceImpl.main(AchievementServiceImpl.java:1151)
参考链接:java.lang.UnsupportedOperationException错误的根源
2)add/remove操作
① list.forEach的增删结果
java
public static void main(String[] args) {
List<String> list= new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add("Tuesday");
list.add("Wednesday");
list.add("Thursday");
list.add("Friday");
list.add("Saturday");
list.forEach(d->{
System.out.println("value="+d);
if (d.equals("Thursday")){
list.add("other");
}
});
}
java
public static void main(String[] args) {
List<String> list= new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add("Tuesday");
list.add("Wednesday");
list.add("Thursday");
list.add("Friday");
list.add("Saturday");
list.forEach(d->{
System.out.println("value="+d);
if (d.equals("Thursday")){
list.remove(d);
}
});
}
list.forEach的add和remove执行结果均报错:
bash
value=Sunday
value=Monday
value=Tuesday
value=Wednesday
value=Thursday
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1262)
at com.meta.image.user.service.impl.AchievementServiceImpl.main(AchievementServiceImpl.java:1158)
② stream.forEach的增删结果
java
public static void main(String[] args) {
List<String> list= new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add("Tuesday");
list.add("Wednesday");
list.add("Thursday");
list.add("Friday");
list.add("Saturday");
list.stream().forEach(d->{
System.out.println("value="+d);
if (d.equals("Thursday")){
list.remove(d);
}
});
}
remove报错:
java
value=Sunday
value=Monday
value=Tuesday
value=Wednesday
value=Thursday
value=Saturday
value=null
Exception in thread "main" java.lang.NullPointerException
at com.meta.image.user.service.impl.AchievementServiceImpl.lambda$main$10(AchievementServiceImpl.java:1160)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647)
at com.meta.image.user.service.impl.AchievementServiceImpl.main(AchievementServiceImpl.java:1158)
java
public static void main(String[] args) {
List<String> list= new ArrayList<>();
list.add("Sunday");
list.add("Monday");
list.add("Tuesday");
list.add("Wednesday");
list.add("Thursday");
list.add("Friday");
list.add("Saturday");
list.stream().forEach(d->{
System.out.println("value="+d);
if (d.equals("Thursday")){
list.add(d);
}
});
}
add报错:
java
value=Sunday
value=Monday
value=Tuesday
value=Wednesday
value=Thursday
value=Friday
value=Saturday
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1390)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:647)
at com.meta.image.user.service.impl.AchievementServiceImpl.main(AchievementServiceImpl.java:1158)
③结果及其补救办法
报错结果可以发现 stream.forEach的remove操作是会迭代整个列表的,纵然输出结果在前,但是依旧输出了null,空指针异常了。
参考我的另一篇博客:ArrayList 循环Remove遇到的坑
所以在应用时间过程中,当真的需要对集合进行add或者remove操作的时候,切记要懂得其结果是什么。
六、 总结
程序中若是最简单的赋值操作,那么list.forEach则满足需求;若需要对list做某些处理,比如筛选,则直接用stream.forEach,何时用parallelStream呢?详情下文源码说明。