为了账号安全,请及时绑定邮箱和手机立即绑定

求助,关于java8 Collectors的groupingBy和mapping

求助,关于java8 Collectors的groupingBy和mapping

犯罪嫌疑人X 2019-01-17 01:57:46
问题描述 小弟尝试将一段如下json读到一个Map<String, List<String>>中,map 的key为condName,map的value为condValue 现在用stream结合Collectors的groupingBy和toList方法,将List<Condition>转成了Map<String, List<Condition>> 然后尝试通过mapping方法将List<Condition>映射为List<String>时发现无从下手。。。几次尝试后得到了一个Map<String, List<List<String>>>,多了一层List 想去掉这一层List,如果再遍历一遍又感觉不太优雅 求助各位有什么思路吗,最好是通过mapping一次得到想要的结果 不胜感激! 问题出现的环境背景及自己尝试过哪些方法 相关代码 { "condition": [ { "condName": "name1", "condValue": [ "val11", "val12" ] }, { "condName": "name2", "condValue": [ "val21" ] }, { "condName": "name3", "condValue": [ "val31", "val32", "val33" ] } ], "total": 3 } @Data public class Request { private List<Condition> condition; private Long total; } @Data public class Condition { private String condName; private List<String> condValue; } 转换方法(读取json和转成Request对象的代码省略。。。): String json = getContent(); try { List<Condition> conditions = deseriliaze(json).getCondition(); Map<String, List<Condition>> map1 = conditions.stream().collect(Collectors.groupingBy(Condition::getCondName)); Map<String, List<List<String>>> map2 = conditions.stream().collect(Collectors.groupingBy(Condition::getCondName, Collectors.mapping(Condition::getCondValue, Collectors.toList()))); } catch (Exception e) { e.printStackTrace(); } 你期待的结果是什么?实际看到的错误信息又是什么? map1: {name3=[Condition(condName=name3, condValue=[val31, val32, val33])], name2=[Condition(condName=name2, condValue=[val21])], name1=[Condition(condName=name1, condValue=[val11, val12])]} map2: {name3=[[val31, val32, val33]], name2=[[val21]], name1=[[val11, val12]]}
查看完整描述

1 回答

?
BIG阳

TA贡献1859条经验 获得超6个赞

回答这个问题的话,我们可以先来看看为啥会出现Map<String, List<List<String>>>的结果,这要从Collectors.groupingBy的设计语义来说了,它代表把流的数据按照一定规则进行归类分组,并要求提供同一组的数据怎么进行收集的方法,所以这就是Collectors.groupingBy两个参数的含义

那题主第一个参数写的是Condition::getCondName,代表流的Condition按照其condName属性进行分组,分组之后,同一组的Condition如何处理,这里题主用的Collectors.mapping(Condition::getCondValue, Collectors.toList()))mapping代表映射转换,这里有点类似分组,Collectors.mapping第一个参数把流的Condition转化为List<String>,此时流里就是List<String>,再经过第二个参数Collectors.toList(),哦豁,当然最后结果就是List<List<String>>

这里题主得不到答案的原因就是Collectors.mapping的第二个参数没有写对,我这里想到三种方式
第一种:还是用Collectors.mapping,类似题主提到的再遍历一遍,哈哈

Map<String, List<String>> collect = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName,
                                Collectors.mapping(Condition::getCondValue,
                                        Collectors.collectingAndThen(Collectors.toList(), lists -> lists.stream().flatMap(List::stream).collect(Collectors.toList())))));

这里Collectors.mapping的第二个参数用了Collectors.collectingAndThen,从名字就看得出来,收集好了之后再做什么事...第一个参数当然还是按照Collectors.toList(),收集到之后,第二个参数再把List遍历一次,压平后再组成List

emmm,我也不太喜欢这种,不过只是引出了哈collectingAndThen,说不定以后题主可以用到

第二种:也还是用Collectors.mapping,不过这次第二个参数用Collectors.reducing

Map<String, List<String>> collect2 = conditions.stream()
                .collect(Collectors.groupingBy(Condition::getCondName, 
                        Collectors.mapping(Condition::getCondValue, 
                                Collectors.reducing(new ArrayList<>(), (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList())))));

这里的Collectors.reducing就是数据的聚合,正好也符合当前的场景,当流里的数据由Condition转化为List<String>时,我们就是要找到一种合并不同List<String>的方法,所以这里用到这个聚合方法Collectors.reducing,第一个参数是起始值,第二个参数代表怎么合并两个list

第二个方法要好一点,不过这里我还想到第三个方法

第三种:不用Collectors.groupingBy,而采用Collectors.toMap

Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, (c1, c2) -> Stream.concat(c1.stream(), c2.stream()).collect(Collectors.toList())));

第三种方式感觉要比前两种简单点,但是这是巧用了哈toMap方法,toMap方法一般使用在数据能够有一种一对一的关系时才用,大多数的时候我们一般也只使用两个参数的方法,即传入怎么获取mapkey和怎么获取mapvalue的两个Function,因为一般情况下,业务上可以保证数据是具有一对一的关系的,如果只是调用两参方法,但是实际使用过程中,确实出现了一对多的情况,那么这时候调用toMap两参方法是会报错的,因此就有了toMap这个三参方法,第三个参数代表怎么合并同一个keyvalue值,所以到了这里就跟第二种方法的思路差不多了,合并两个集合即可

以上就是我的回答,仅供参考

对了,再加一点,我一般是不太喜欢在流里写过长的lambda表达式的,因为流里是要体现当前流程的,不是烂七八糟的都往里噻,所以第三种方式,最后的集合合并,最好还是写成一个BinaryOperator,毕竟现在方法也可以是一种参数或者属性了嘛

public static final BinaryOperator<List<String>> listMergeMethod = (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList());
Map<String, List<String>> collect1 = conditions.stream().collect(
                Collectors.toMap(Condition::getCondName, Condition::getCondValue, listMergeMethod));

这样我感觉要顺点。。。

查看完整回答
1 反对 回复 2019-02-12
  • 无道572
    无道572
    楼主回答的非常之好,学到了,感激不尽~
  • 1 回答
  • 0 关注
  • 9271 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号