
本文介绍如何使用 Java Stream API 对具有相同 ID 的 Test 对象进行分组合并,自动填充各字段的非 null 值,最终得到每个 ID 对应的完整对象列表,避免手动遍历和 Map 操作。
本文介绍如何使用 java stream api 对具有相同 id 的 `test` 对象进行分组合并,自动填充各字段的非 null 值,最终得到每个 id 对应的完整对象列表,避免手动遍历和 map 操作。
在实际开发中,常遇到「同一实体的多个片段对象」需按主键(如 id)合并为完整对象的场景:例如前端分步提交、微服务间数据拼接、或 ETL 中字段分散在不同记录中。Java Stream API 本身不支持类似 Flink/Spark 的“keyed stream”直接聚合,但可通过 groupingBy + values() + map/reduce 组合实现优雅合并。
核心思路是三步链式处理:
-
分组:按
id将对象归入Map<integer list>></integer>; -
提取值流:调用
.values().stream()转为Stream<list>></list>; -
合并每组:对每个
List<test></test>使用reduce,逐字段取首个非 null 值构造新对象。
以下是完整可运行示例(含 Lombok 简化代码):
import lombok.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestMergeExample {
public static void main(String[] args) {
Test test1 = new Test(1, "qweqwe", null);
Test test2 = new Test(2, "qweqwe", null);
Test test3 = new Test(3, "qweqwe", null);
Test test4 = new Test(1, null, "dfdsfg");
Test test5 = new Test(2, null, "dfdsfg");
Test test6 = new Test(3, null, "dfdsfg");
List<Test> merged = Stream.of(test1, test2, test3, test4, test5, test6)
.collect(Collectors.groupingBy(Test::getId))
.values()
.stream()
.map(group -> group.stream()
.reduce(new Test(), // identity: 空对象作为初始值(需确保构造函数安全)
(acc, curr) -> new Test(
acc.getId() != null ? acc.getId() : curr.getId(),
acc.getFirst() != null ? acc.getFirst() : curr.getFirst(),
acc.getSecond() != null ? acc.getSecond() : curr.getSecond()
),
(a, b) -> a // combiner(并行流需,此处单线程可忽略,但保留更健壮)
))
.collect(Collectors.toList());
System.out.println(merged); // [(1, qweqwe, dfdsfg), (2, qweqwe, dfdsfg), (3, qweqwe, dfdsfg)]
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Test {
private Integer id;
private String first;
private String second;
@Override
public String toString() {
return String.format("(%d, %s, %s)", id, first, second);
}
}
}⚠️ 注意事项:
立即学习“Java免费学习笔记(深入)”;
-
空值安全:
reduce(identity, accumulator, combiner)比reduce(BinaryOperator)更安全,能避免Optional.orElse()的冗余判断,且天然支持并行流; -
性能考量:每次
reduce都新建对象,若数据量极大(>10⁵),建议改用传统for循环 +Map<integer test></integer>缓存合并结果; -
字段逻辑扩展:如需支持数值累加、字符串拼接或取最新时间戳,只需修改
accumulator中对应字段逻辑; -
构造函数约束:确保
Test的无参构造函数可用(Lombok@NoArgsConstructor已覆盖),否则identity参数需显式构造。
总结:该方案以声明式风格达成目标,代码简洁、语义清晰,适用于中等规模数据与快速原型开发;生产环境若追求极致性能或复杂合并策略(如冲突检测、版本控制),推荐封装为专用工具类,并辅以单元测试验证空值与边界场景。


















