Java 流 - 分组依据和 return 嵌套映射

Java Streams - group-by and return a Nested Map

我的数据是这样的,

unitId  time  value1 value2
 a      2021    10    11   
 a      2022    15    13
 b      2021    20    25
 b      2022    30    37

我的目标是将每个 unitId 和值放入这样的地图中,

{
  'a': {'2021_value1': 10, '2021_value2': 11, '2022_value1': 15, '2022_value2': 13},
  'b': {'2021_value1': 20, '2021_value2': 25, '2022_value1': 30, '2022_value2': 37},
}

我已经找到了两种实现方法,这是我的代码,

public class Unit {

    public String unitId;

    public Integer year;

    public Integer value1;

    public Integer value2;

    public static Unit of(String unitId, Integer year, Integer value1, Integer value2) {
        Unit unit = new Unit();
        unit.unitId = unitId;
        unit.year = year;
        unit.value1 = value1;
        unit.value2 = value2;
        return unit;
    }

}

并且

public class UnitTest {

    private static void printMap(Map<String, Map<String, Integer>> map) {
        map.forEach((k, v) -> {
            String vStr = v.entrySet().stream().map(a -> String.format("%s: %s", a.getKey(), a.getValue())).collect(Collectors.joining(", "));
            System.out.printf("%s: {%s}%n", k, vStr);
        });
    }

    public static void main(String[] args) {
        List<Unit> list = new ArrayList<>();
        list.add(Unit.of("a", 2021, 10,  11 ));
        list.add(Unit.of("a", 2022, 15,  13));
        list.add(Unit.of("b", 2021, 20,  25));
        list.add(Unit.of("b", 2022, 30,  37));

        Map<String, Map<String, Integer>> map1 = list.stream().collect(
            Collectors.groupingBy(
                x -> x.unitId,
                Collector.of(
                    HashMap::new,
                    (x, y) -> {
                        x.put(String.format("%s_%s", y.year, "value1"), y.value1);
                        x.put(String.format("%s_%s", y.year, "value2"), y.value2);
                    },
                    (x, y) -> {x.putAll(y); return x;}
                )
            )
        );

        Map<String, Map<String, Integer>> map2 = list.stream().collect(
            Collectors.groupingBy(
                x -> x.unitId,
                Collectors.collectingAndThen(
                    Collectors.toList(),
                    x -> x.stream()
                        .flatMap(y -> Stream.of(
                                    new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value1"), y.value1),
                                    new AbstractMap.SimpleEntry<>(String.format("%s_%s", y.year, "value2"), y.value2)
                             ))
                        .collect(Collectors.toMap(
                                     AbstractMap.SimpleEntry::getKey, 
                                     AbstractMap.SimpleEntry::getValue)))
            )
        );
        printMap(map1);
        printMap(map2);
    }
}

第一个更像是手动编写处理,第二个使用可能没有必要的临时列表。有什么直接或简单的方法可以做到这一点,比如使用 Collectors.toMap API 或其他方法?

Is there any direct or simple way to do this, like use Collectors.toMap API or something else?

如果您只想使用 built-in 个收集器,您可以尝试组合使用 groupingBy()teeing()

Collectors.teeing() 需要三个 参数 2 下游 收集器 和一个 merger 函数。流中的每个元素都将传递到两个 收集器 中,当这些收集器完成后,它们产生的结果将由 函数 合并。

在下面的代码中,toMap() 用作 teeing() 下游收集器 。这些 收集器 中的每一个都负责检索其类型的 value.

代码可能如下所示:

public static void main(String[] args) {
    List<Unit> list =
        List.of(Unit.of("a", 2021, 10,  11 ),
                Unit.of("a", 2022, 15,  13),
                Unit.of("b", 2021, 20,  25),
                Unit.of("b", 2022, 30,  37));

    Map<String, Map<String, Integer>> map = list.stream()
        .collect(Collectors.groupingBy(Unit::getUnitId,
            Collectors.teeing(
                Collectors.toMap(
                    unit -> unit.getYear() + "_value1",
                    Unit::getValue1),
            Collectors.toMap(
                    unit -> unit.getYear() + "_value2",
                    Unit::getValue2),
                (values1, values2) -> {values1.putAll(values2); return values1;})
        ));

    printMap(map);
}

输出:

a: {2022_value2: 13, 2021_value1: 10, 2022_value1: 15, 2021_value2: 11}
b: {2022_value2: 37, 2021_value1: 20, 2022_value1: 30, 2021_value2: 25}

注:

  • 如果考虑性能,Collector.of() 会稍微好一些,因为它不会创建中间集合。
  • 为了使这种方法正常工作(我指的是上面列出的代码以及问题中的代码),unitIdyear 的每个组合都应该是唯一的。否则,请考虑添加解决重复项的逻辑。