求助,关于java8 Collectors的groupingBy和mapping

问题描述

  • 小弟尝试将一段如下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]]}
阅读 16.8k
1 个回答

回答这个问题的话,我们可以先来看看为啥会出现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));

这样我感觉要顺点。。。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏