【问题标题】:group list of complex object using java stream使用java流的复杂对象的组列表
【发布时间】:2021-02-02 02:45:12
【问题描述】:

我有一个 gprsEvents 列表,其中列表中的每个元素都是 Map ,如下所示。我需要:

{
   "events":[
      {
         "localTimeStamp":"20170523113305",
         "serviceCode":"GPRS",
         "recEntityCode":[
            "1",
            "2"
         ],
         "index":"1",
         "dataVolumeIncoming":"400000",
         "dataVolumeOutgoing":"27600",
         "callChargingId":"4100853125",
      },
      {
         "localTimeStamp":"20190523113305",
         "serviceCode":"GPRS",
         "recEntityCode":[
            "2",
            "4"
         ],
         "index":"2",
         "dataVolumeIncoming":"300000",
         "dataVolumeOutgoing":"47600",
         "callChargingId":"4100853125",
      },
      {
         "localTimeStamp":"20180523113305",
         "serviceCode":"GPRS",
         "recEntityCode":[
            "1",
            "2"
         ],
         "index":"7",
         "dataVolumeIncoming":"100000",
         "dataVolumeOutgoing":"17600",
         "callChargingId":"5100853125",
      }
      
   ]
}
  1. 按字段“callChargingId”对列表中的元素进行分组。
  2. 分组的结果应该是一个Map,其中键是“callChargingIds”,是一个单独的元素,其中每个字段都包含一组作为分组结果的字段。结果应该如下所示:
{
   "5100853125":[
      {
         "localTimeStamp":"20180523113305",
         "serviceCode":"GPRS",
         "recEntityCode":[
            "1",
            "2"
         ],
         "index":"7",
         "dataVolumeIncoming":"100000",
         "dataVolumeOutgoing":"17600"
      }
   ],
   "4100853125":[
      {
         "localTimeStamp":[
            "20170523113305",
            "20190523113305"
         ],
         "serviceCode":"GPRS",
         "recEntityCode":[
            [
               "1",
               "2"
            ],
            [
               "2",
               "4"
            ]
         ],
         "index":[
            "1",
            "2"
         ],
         "dataVolumeIncoming":"700000",
         "dataVolumeOutgoing":"75200"
      }
   ]
}

对于某些字段,分组的结果应该是一个列表(timeStamp、recEntityCode 和索引),而在其他字段中,结果应该是总和(dataVolumeIncoming 和 dataVolumeOutgoing

我开始考虑使用 java 8 流(groupingBy): gprsEvents.stream().collect(Collectors.groupingBy(map -> map.get("callChargingId").toString()))

我现在坚持要获得合适的结果,特别是要在一个 Map 和上述字段的列表或总和中获得结果......

【问题讨论】:

    标签: json java-8 stream grouping collectors


    【解决方案1】:

    这里的关键点是实现BinaryOperator&lt;U&gt; mergeFunction 对象,它将完成最复杂的部分:合并两个Map 实例。我建议使用带有 3 个参数的方法:toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator mergeFunction):

    1. keyMapper - callChargingId 值,
    2. valueMapper - Map 实例
    3. mergeFunction - 合并将合并两个 BinaryOperator&lt;Map&lt;String, Object&gt;&gt; 类型的实例

    简单的实现可能如下所示:

    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.fasterxml.jackson.databind.type.MapType;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.function.BinaryOperator;
    import java.util.stream.Collectors;
    
    public class JsonTypeInfoApp {
        public static void main(String[] args) throws IOException {
            File jsonFile = new File("./resource/test.json").getAbsoluteFile();
    
            JsonMapper mapper = JsonMapper.builder()
                    .enable(SerializationFeature.INDENT_OUTPUT)
                    .build();
            MapType jsonType = mapper.getTypeFactory().constructMapType(Map.class, String.class, List.class);
            Map<String, List<Map<String, Object>>> response = mapper.readValue(jsonFile, jsonType);
    
            List<Map<String, Object>> gprsEvents = response.get("events");
            Map<Object, Map<String, Object>> result = gprsEvents.stream()
                    .collect(Collectors.toMap(
                            map -> map.get("callChargingId"),
                            map -> map,
                            new EventMerger()));
    
            mapper.writeValue(System.out, result);
        }
    }
    
    class EventMerger implements BinaryOperator<Map<String, Object>> {
    
        @Override
        public Map<String, Object> apply(Map<String, Object> map0, Map<String, Object> map1) {
            map1.forEach((secondKey, secondValue) -> {
                map0.compute(secondKey, (key, value) -> {
                    if (value == null) {
                        return secondValue;
                    } else if (value instanceof Set) {
                        Set<Object> values = (Set<Object>) value;
                        values.add(secondValue);
                        return values;
                    }
    
                    Set<Object> values = new HashSet<>();
                    values.add(value);
                    values.add(secondValue);
    
                    return values;
                });
            });
            return map0;
        }
    }
    

    上面的代码打印:

    {
      "4100853125" : {
        "localTimeStamp" : [ "20170523113305", "20190523113305" ],
        "serviceCode" : [ "GPRS" ],
        "recEntityCode" : [ [ "1", "2" ], [ "2", "4" ] ],
        "index" : [ "1", "2" ],
        "dataVolumeOutgoing" : [ "47600", "27600" ],
        "callChargingId" : [ "4100853125" ],
        "dataVolumeIncoming" : [ "400000", "300000" ]
      },
      "5100853125" : {
        "localTimeStamp" : "20180523113305",
        "serviceCode" : "GPRS",
        "recEntityCode" : [ "1", "2" ],
        "index" : "7",
        "dataVolumeIncoming" : "100000",
        "dataVolumeOutgoing" : "17600",
        "callChargingId" : "5100853125"
      }
    }
    

    【讨论】:

    • 无需每次合并时都创建新地图。假设您在值映射器中创建了地图的副本,添加到左侧地图就足够了。另外,为什么不使用方法引用而不是新类?
    • @fps: 1. new map - 好点。我只是想明确表明它将是一个merging 点,但没有理由创建一个新实例。也许与parallelStream 一起使用会更安全?你怎么看? 2. why not using a method reference - 只是为了清楚地显示我在答案顶部提到的 3 个论点。当然,最终的解决方案取决于作者,可以很容易地移入内部作为方法参考。当我们创建这样的类时,提供单元测试也容易得多。
    • parallelStream 在大多数情况下使用速度较慢或不正确,但与安全无关。你要么平行,要么不平行。至于使用方法参考,你说得对,这是个人喜好问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    相关资源
    最近更新 更多