问题：求助，关于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]]}
```

回答：回答这个问题的话，我们可以先来看看为啥会出现`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`方法一般使用在数据能够有一种一对一的关系时才用，大多数的时候我们一般也只使用两个参数的方法，即传入怎么获取`map`的`key`和怎么获取`map`的`value`的两个`Function`，因为一般情况下，业务上可以保证数据是具有一对一的关系的，如果只是调用两参方法，但是实际使用过程中，确实出现了一对多的情况，那么这时候调用`toMap`两参方法是会报错的，因此就有了`toMap`这个三参方法，第三个参数代表怎么合并同一个`key`的`value`值，所以到了这里就跟第二种方法的思路差不多了，合并两个集合即可
以上就是我的回答，仅供参考
对了，再加一点，我一般是不太喜欢在流里写过长的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));
```
这样我感觉要顺点。

标签: java, stream, group-by, json