Java 8 Stream:按数组指定多字段分组并累加对应元素
问题:List<String[]>分组并累加指定字段生成新集合
现有List<String[]>类型数据集,示例数据如下:
List<String[]> dataLines = List.of( new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "84M", "-101.87", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-102.48", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-103.75", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-110.39", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "CZK", "12670012.4055", "60M", "-103.75", "0"}, new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4066", "20M", "-10.8", "0"} );
需要生成新的List<String[]>集合newDataLine,规则为:将数组中第0、1、3、5位元素相同的项归为一组,累加每组中数组的第6位元素,预期输出如下:
["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","84M","-101.87","0"], ["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","120M","-124.08000000000001","0"], ["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","60M","-214.14","0"], ["2002","BRBTSS","BRSTNCNTF212","CZK","12670012.4055","60M","-103.75","0"], ["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4066","20M","-10.8","0"]
已尝试嵌套分组代码:
Map<String, Map<String, Map<String, Map<String, Double>>>> map = dataLines.stream() .collect(Collectors.groupingBy( s -> s[0], Collectors.groupingBy(s -> s[1], Collectors.groupingBy(s -> s[3], Collectors.groupingBy(s -> s[5], Collectors.summingDouble(s -> Double.valueOf(s[6]))))) );
得到的输出为:
{2002={BRBTSS={BRL={84M=-101.87, 60M=-214.14, 120M=-124.08000000000001}}}}
请问如何实现符合需求的分组转换?
解决方案
你已经通过嵌套Map拿到了分组后的累加值,接下来只需要将Map结构转换回目标List<String[]>即可,这里提供两种实现方式:
方式一:使用自定义分组键(推荐,Java 16+)
通过定义一个记录类封装分组依据和需要保留的字段,直接一次性完成分组与转换,无需二次遍历原数据:
// 定义分组键记录,包含分组字段和需保留的其他字段 record GroupKey(String year, String code1, String currency, String term, String code2, String amount) {} List<String[]> newDataLine = dataLines.stream() // 按自定义GroupKey分组,累加第6位的数值 .collect(Collectors.groupingBy( s -> new GroupKey(s[0], s[1], s[3], s[5], s[2], s[4]), Collectors.summingDouble(s -> Double.valueOf(s[6])) )) // 将分组结果转换为目标String[]数组 .entrySet() .stream() .map(entry -> { GroupKey key = entry.getKey(); Double sum = entry.getValue(); return new String[]{ key.year(), key.code1(), key.code2(), key.currency(), key.amount(), key.term(), sum.toString(), "0" // 第7位固定为0,若需动态取可从原数据补充 }; }) .collect(Collectors.toList());
方式二:基于现有嵌套Map转换
无需创建额外类,直接遍历已生成的嵌套Map,从原数据中补充分组中未包含的字段:
List<String[]> newDataLine = new ArrayList<>(); // 遍历嵌套Map的每一层 map.forEach((year, code1Map) -> { code1Map.forEach((code1, currencyMap) -> { currencyMap.forEach((currency, termMap) -> { termMap.forEach((term, sum) -> { // 从原数据中找到该组的任意一条记录,获取第2、4、7位的值(假设同组这些值一致) String[] sample = dataLines.stream() .filter(s -> s[0].equals(year) && s[1].equals(code1) && s[3].equals(currency) && s[5].equals(term)) .findFirst() .orElseThrow(); // 组装目标数组 newDataLine.add(new String[]{ year, code1, sample[2], currency, sample[4], term, sum.toString(), sample[7] }); }); }); }); });
说明
- 方式一效率更高,避免了二次查找原数据,代码更简洁,适合Java 16及以上版本;
- 方式二无需额外定义类,兼容性更好,但需要遍历原数据查找样本,性能略低。
两种方式最终都会生成符合预期的newDataLine集合。
内容的提问来源于stack exchange,提问作者prachi Kadam
相关产品推荐
相关产品推荐

