Java Streams:将分组结果映射为自定义对象列表

本文介绍如何使用 java stream api 将按字段分组的列表(如 `list`)高效转换为固定结构的自定义对象列表(如 `list`),并安全处理不等长分组(自动补 `null`)。

在实际开发中,常需将扁平数据按某字段(如 value)聚合并展开为结构化对象。例如,给定 PivotMapEgModel 列表,目标是生成 List,其中每个 ResultSet 包含 value 及最多三个 code 字段(code_1、code_2、code_3),不足则补 null。

核心思路分为三步:

  1. 分组聚合:用 Collectors.groupingBy 按 getValue() 分组,并用 Collectors.mapping 提取所有 getCode() 值为 List
  2. 转换条目:对分组结果的 entrySet() 流式遍历,将每个 (key, List) 映射为 ResultSet;
  3. 安全取值:通过辅助方法 getCode(list, index) 防止 IndexOutOfBoundsException,索引越界时返回 null。

以下是完整可运行示例:

import java.util.*;
import java.util.stream.Collectors;

@Data
@AllArgsConstructor
class ResultSet {
    long value;
    String code_1;
    String code_2;
    String code_3;
}

class PivotMapEgModel {
    private final long value;
    private final String code;

    PivotMapEgModel(long value, String code) {
        this.value = value;
        this.code = code;
    }

    long getValue() { return value; }
    String getCode() { return code; }
}

// 主逻辑
public class StreamToCustomObject {
    public static void main(String[] args) {
        List pivotMapList = List.of(
            new PivotMapEgModel(1L, "1"),
            new PivotMapEgModel(1L, "2"),
            new PivotMapEgModel(1L, "3"),
            new PivotMapEgModel(2L, "5")
        );

        List result = pivotMapList.stream()
            .collect(Collectors.groupingBy(
                PivotMapEgModel::getValue,
                Collectors.mapping(PivotMapEgModel::getCode, Collectors.toList())
            ))
            .entrySet()
            .stream()
            .map(entry -> new ResultSet(
                entry.getKey(),
                getCode(entry.getValue(), 0),
                getCode(entry.getValue(), 1),
                getCode(entry.getValue(), 2)
            ))
            .collect(Collectors.toList());

        System.out.println(result);
        // 输出: [ResultSet(value=1, code_1=1, code_2=2, code_3=3), 
        //        ResultSet(value=2, code_1=5, code_2=null, code_3=null)]
    }

    private static String getCode(List codes, int index) {
        return index < codes.size() ? codes.get(index) : null;
    }
}

关键注意事项

  • 分组后直接操作 entrySet().stream() 是连接聚合与构造的关键桥梁;
  • 避免在 map() 中硬编码 list.get(i),必须封装边界检查,否则流式处理会因单个异常而中断整个流水线;
  • 若业务允许,也可用 Optional.ofNullable(...).orElse(null) 等方式增强可读性,但本例中自定义 getCode 更简洁高效;
  • 此方案时间复杂度为 O(n),仅一次遍历 + 一次分组后遍历,性能优秀且语义清晰。

该模式适用于任何“分组 → 展开为固定宽表”的场景,是 Java Stream 进阶应用的典型范式。