
本文介绍一种基于 java stream 和 hashset 的高效方案,用于批量处理产品列表:对名称重复的产品拼接 category 名称生成 frontname,对唯一名称产品则直接赋值 name。方法时间复杂度接近 o(n),避免嵌套遍历,兼顾可读性与性能。
在实际业务开发中,我们常需根据字段的“唯一性”对对象进行差异化处理。以 Product 类为例:
@Data
public class Product {
private UUID id;
private String name;
private String categoryName;
private String frontName;
}目标是:
✅ 若某 name 在整个列表中出现多次 → frontName = name + "," + categoryName
✅ 若某 name 仅出现一次 → frontName = name
✅ 推荐解法:两阶段流式处理(O(n) 时间复杂度)
核心思路:先识别所有重复 name,再统一更新。利用 Set.add() 返回值(false 表示已存在)在单次遍历中收集重复项:
// 第一阶段:提取所有重复的 product name Setseen = new HashSet<>(); Set duplicateNames = productList.stream() .map(Product::getName) .filter(name -> !seen.add(name)) // add() 返回 false → 已存在 → 是重复项 .collect(Collectors.toSet()); // 第二阶段:遍历原列表,按重复性设置 frontName productList.forEach(product -> { if (duplicateNames.contains(product.getName())) { product.setFrontName(product.getName() + "," + product.getCategoryName()); } else { product.setFrontName(product.getName()); } });
? 关键点说明: seen.add(name) 在首次遇到 name 时返回 true,后续重复调用返回 false; filter(...) 保留的是所有 第二次及以后出现 的 name,因此 duplicateNames 准确涵盖所有重复值; 整体仅需两次线性遍历(Stream + forEach),无嵌套循环,性能稳定。
⚠️ 注意事项与优化建议
- 线程安全:上述代码适用于单线程场景。若在并发环境中操作 productList,需确保 productList 本身线程安全,或加锁同步 forEach 操作。
-
空值防护:生产环境建议增加 Objects.nonNull(product.getName()) 判断,避免 NullPointerException:
.filter(Objects::nonNull)
-
不可变集合(进阶):如需更高安全性,可将 duplicateNames 转为不可变集合:
Set
duplicateNames = Collections.unmodifiableSet( productList.stream() .map(p -> Optional.ofNullable(p.getName()).orElse("")) .filter(name -> !seen.add(name)) .collect(Collectors.toSet()) ); -
替代方案对比:
- ❌ 使用 groupingBy 统计频次(如 Collectors.groupingBy(Product::getName, Collectors.counting()))虽语义清晰,但需额外 Map 存储和二次遍历,内存开销略高;
- ✅ 当前方案用 HashSet 状态标记,空间更省,适合大数据量场景。
该方案简洁、高效、易维护,是处理“按字段重复性差异化赋值”类需求的典型范式。










