【问题标题】:Grouping by object - Java streams按对象分组 - Java 流
【发布时间】:2018-05-30 06:20:24
【问题描述】:

我有一个数据列表,如下所示:

List<Data> data = new ArrayList<Data>();
data.add(new Data("d1", "option1"));
data.add(new Data("d2", "option1"));
data.add(new Data("d1", "option2"));
data.add(new Data("d3", "option1"));
data.add(new Data("d3", "option2"));
data.add(new Data("d3", "option3"));

结构如下:

class Data {
    private String name;
    private String option;
    private List<String> options = new ArrayList<>();
    public Data(String name, String option) {
        this.name = name;
        this.option = option;
    }

    public void addOption(String option) {
        options.add(option);
    }
}

如何根据名称及其选项将项目分组到一个新数组中,

[
"d1": {
    "name": "d1",
    "options": ["option1", "option2"]
},
"d2": {
    "name": "d2",
    "options": ["option1"]
},
"d3": {
    "name": "d3",
    "options": ["option1", "option2", "option3"]
}
]

【问题讨论】:

  • 澄清一下:您希望输出为 JSON 字符串吗?或者有一种方法可以在Data 的新实例的options 字段中获取所有option 字段?对于后者,我已经建议更改一些内容,因为如果它们的含义基本相同,那么在您的类中同时拥有选项列表和单个 String 选项是很奇怪的。
  • 不能实例化List;这是一个界面。为什么Data 有一个option 和一个options 列表?你永远不会打电话给addOption。此外,您不能将它们分组到一个数组中。你的意思是Map
  • 没有 JSON,为了简洁,我给出了这样的对象结构。

标签: java collections java-8 java-stream


【解决方案1】:

您可以使用Collectors.toMap 收集器:

Map<String,Data>
    grouped = data.stream()
                  .collect(Collectors.toMap(Data::getName,
                                            d -> new Data(d.getName(),d.getOption()),
                                            (d1,d2) -> {d1.addOptions(d2.getOptions()); return d1;});

这将需要更改 Data 构造函数以将传递的选项添加到选项 List 以及添加一个 addOptions 方法来接收选项列表并将所有选项添加到选项 List 的当前实例。

【讨论】:

  • 这种方法对我来说看起来很方便,我从结果映射中得到“数据”类型的集合作为值,而不是 List 类型。
【解决方案2】:

试试这个,

final Map<String, List<String>> optionsByName = dataList.stream().collect(
        Collectors.groupingBy(Data::getName, Collectors.mapping(Data::getOption, Collectors.toList())));

我建议您使用此 map 作为源来创建您需要的 DTO,而不会弄乱您的代码以获得您想要的确切结果。

【讨论】:

    【解决方案3】:

    您可以在流上使用简单的分组收集器:

    Map<String, List<String>> optionMap = data.stream()
                .collect(Collectors.groupingBy(Data::getName, 
                         Collectors.mapping(Data::getOption, Collectors.toList())));
    

    当使用您的测试列表进行测试时,上面会产生以下输出:

    {d1=[option1, option2], d2=[option1], d3=[option1, option2, option3]}
    

    以上似乎是您所需地图的更好、类型安全的替代方案。此外,它还避免了不必要的重复。

    您的最终地图仍然可以根据上述情况进行计算:

    Map<String, Map<String, Object>> m = new HashMap<>();
    optionMap.entrySet().forEach(entry -> {
        Map<String, Object> map = new HashMap<>();
        map.put("name", entry.getKey());
        map.put("options", entry.getValue());
    
        m.put(entry.getKey(), map);
    });
    

    上面的m 映射如下:

    {d1={name=d1, options=[option1, option2]}, 
     d2={name=d2, options=[option1]}, 
     d3={name=d3, options=[option1, option2, option3]}}
    

    这正是你所需要的,但它似乎包含重复的数据,而且它的类型安全性不如上一个结果中的简单映射。

    【讨论】:

      猜你喜欢
      • 2020-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多