设为首页 加入收藏

TOP

再聊Java Stream的一些实战技能与注意点(二)
2023-09-23 15:44:34 】 浏览:275
Tags:再聊 Java Stream 能与注

大部分情况下,掌握这一点,已经可以应付日常的开发过程中对Stream代码逻辑的debug诉求了。但是上述过程偏向于细节,如果需要看下整个Stream代码段整体层面的执行与数据变化过程,就需要上面提到的Stream Trace功能。要想使用该功能,断点的位置也是有讲究的,必须要将断点打在stream开流的地方,否则看不到任何内容。另外,对于一些新版本的IDEA而言,这个入口也比较隐蔽,藏在了下拉菜单中,就像下面这个样子。

我们找到Trace Current Stream Chain并点击,可以打开Stream Trace界面,这里以chain链的方式,和stream代码块逻辑对应,分步骤展示了每个stream处理环节的执行结果。比如我们以 filter环节为例,窗口中以左右视图的形式,左侧显示了原始输入的内容,右侧是经过filter处理后符合条件并保留下来的数据内容,并且还有连接线进行指引,一眼就可以看出哪些元素是被过滤舍弃了的:

不止于此,Stream Trace除了提供上述分步查看结果的能力,还支持直接显示整体的链路执行全貌。点击Stream Trace窗口左下角的 Flat Mode按钮即可切换到全貌模式,可以看到最初原始数据,如何一步步被处理并得到最终的结果。

看到这里,以后还会说Stream不好调试吗?至少我不会了。

小心Collectors.toMap出现key值重复报错

在我们常规的HashMap的 put(key,value)操作中,一般很少会关注key是否已经在map中存在,因为put方法的策略是存在会覆盖已有的数据。但是在Stream中,使用 Collectors.toMap方法来实现的时候,可能稍不留神就会踩坑。所以,有小伙伴在评论区热心的提示,在使用此方法的时候需要手动加上 mergeFunction以防止key冲突。

这个究竟是怎么回事呢?我们看下面的这段代码:

public void testCollectStopOptions() {
    List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(22));
    // collect成HashMap,key为id,value为Dept对象
    Map<Integer, Dept> collectMap = ids.stream()
            .collect(Collectors.toMap(Dept::getId, dept -> dept));
    System.out.println("collectMap:" + collectMap);
}

执行上述代码,不出意外的话会出意外。如下结果:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Dept{id=22}
	at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
	at java.util.HashMap.merge(HashMap.java:1254)
	at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.eva luateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.eva luate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

因为在收集器进行map转换的时候,由于出现了重复的key,所以抛出异常了。 为什么会出现异常呢?为什么不是以为的覆盖呢?我们看下源码的实现逻辑:

可以看出,默认情况下如果出现重复key值,会对外抛出IllegalStateException异常。同时,我们看到,它其实也有提供重载方法,可以由使用者自行指定key值重复的时候的执行策略:

所以,我们的目标是出现重复值的时候,使用新的值覆盖已有的值而非抛出异常,那我们直接手动指定下让toMap按照我们的要求进行处理,就可以啦。改造下前面的那段代码,传入自行实现的 mergeFunction函数块,即指定下如果key重复的时候,以新一份的数据为准:

    public void testCollectStopOptions() {
        List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(22));
        // collect成HashMap,key为id,value为Dept对象
        Map<Integer, Dept> collectMap = ids.stream()
                .collect(Collectors.toMap(
                        Dept::getId,
                        dept -> dept,
                        (exist, newOne) -> newOne));
        System.out.println("collectMap:" + collectMap);
    }

再次执行,终于看到我们预期中的结果了:

collectMap:{17=Dept{id=17}, 22=Dept{id=22}}

By The Way,个人感觉JDK在这块的默认实现逻辑有点不合理。虽然现在默认的抛异常方式,可以强制让使用端感知并去指定自己的逻辑,但这默认逻辑与map的put操作默认逻辑不一致,也让很多人都会无辜踩坑。如果将默认值改为有则覆盖的方式,或许会更符合常理一些 —— 毕竟被广泛使用的HashMap的源码里,put操作默认就是覆盖的,不信可以看HashMap源码的实现逻辑:

慎用peek承载业务处理逻辑

peekforeach在Stream流操作中,都可以实现对元素的遍历操作。区别点在与peek属于中间方法,而foreach属于终止方法。这也就意味着peek只能作为管道中途的一个处理步骤,而没法直接执行得到结果,其后面必须还要有其它终止操作的时候才会被执行;而foreach作为无返回值的终止方法,则可以直接执

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇什么是方法重载?返回值算重载吗? 下一篇ArrayList/MySQL数据批量写入Exce..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目