
本文介绍一种基于函数式编程思想的通用方案,使用泛型 + Function 接口封装重复的 Stream 处理逻辑,实现按需选择键字段(如 carNumber、colorId 等)进行过滤、分组与合并,避免代码冗余并提升可维护性。
本文介绍一种基于函数式编程思想的通用方案,使用泛型 + `function` 接口封装重复的 stream 处理逻辑,实现按需选择键字段(如 carnumber、colorid 等)进行过滤、分组与合并,避免代码冗余并提升可维护性。
在实际开发中,我们常遇到多个高度相似的 Stream 操作:仅在键提取器(key mapper)、过滤条件或合并策略上存在细微差异。例如,对 Car 列表分别按 carNumber 和 carColorId 构建去重/聚合映射时,若为每种字段单独编写 stream().collect(...),不仅违反 DRY 原则,也增加后期维护成本。
解决这一问题的核心思路是将变化点抽象为参数——即把“用哪个字段作 key”“该字段是否为空”“冲突时如何合并”等逻辑外化为函数式参数。以下是一个简洁、类型安全且可复用的通用方法:
static <T> Map<T, Car> collectCars(List<Car> carList, Function<Car, T> keyMapper) {
return carList.stream()
.filter(car -> !ObjectUtils.isEmpty(keyMapper.apply(car))) // 动态过滤:检查目标字段非空
.collect(Collectors.toMap(
keyMapper, // 动态键提取
Function.identity(), // 值为 Car 本身
(x1, x2) -> new Car(
x1.getCarNumber() != null ? x1.getCarNumber() : x2.getCarNumber(),
x1.getCarColorId() != null ? x1.getCarColorId() : x2.getCarColorId(),
null, // 注意:原示例中构造函数未匹配字段,此处需修正
x1.getEngineNumber().concat(x2.getEngineNumber()),
x1.getPartsNumber().concat(x2.getPartsNumber()),
"MERGED" // 合并后的 modifiedBy 可按需设定
)
));
}⚠️ 重要注意事项:
- 原答案中 new Car(...) 的构造参数顺序与 @Data 类字段不一致,实际使用时必须严格匹配 Car 的构造签名(推荐使用全参构造器或 Builder 模式)。若 Car 无合适构造器,建议改用 BeanUtils.copyProperties 或手动 set 字段构建合并对象。
- ObjectUtils.isEmpty() 对 Long 类型判断需谨慎:它会将 0L 视为“空”,若业务中 0 是合法值,请改用 Objects.nonNull(x) 或显式判零逻辑。
- 合并策略 (x1, x2) -> {...} 应明确业务语义(如取最新修改者、拼接字符串、取最大 ID),不可简单套用模板。
调用示例如下,支持完全动态切换维度:
立即学习“Java免费学习笔记(深入)”;
// 按 carNumber 聚合 Map<Long, Car> byNumber = collectCars(carList, Car::getCarNumber); // 按 carColorId 聚合 Map<Long, Car> byColor = collectCars(carList, Car::getCarColorId); // 甚至可扩展至 String 字段(如 engineNumber) Map<String, Car> byEngine = collectCars(carList, Car::getEngineNumber);
进一步地,若需根据字符串参数(如 "colorId")自动路由到对应 getter,可结合 Map
private static final Map<String, Function<Car, Object>> KEY_STRATEGIES = Map.of(
"carNumber", Car::getCarNumber,
"carColorId", Car::getCarColorId,
"engineNumber", Car::getEngineNumber
);
// 动态调用
String strategy = "carColorId";
@SuppressWarnings("unchecked")
Function<Car, Long> mapper = (Function<Car, Long>) KEY_STRATEGIES.get(strategy);
Map<Long, Car> result = collectCars(carList, mapper);这种设计本质上是策略模式(Strategy Pattern)在函数式编程中的自然体现:将算法行为参数化,使客户端能在运行时灵活组合不同策略,同时保持核心流程稳定。它显著提升了代码内聚性、可测试性与可扩展性,是处理多维数据聚合场景的推荐实践。


















