
本文介绍如何在 Java 中对 List 按 productId 去重,保留每个 ID 下 productTitle 字典序最小的那条记录(即“留一去多”,且依据可定制条件择优),使用 Stream + Collectors.groupingBy + maxBy 实现高效、声明式处理。
本文介绍如何在 java 中对 list
在实际业务开发中,我们常需处理来自遗留系统或外部接口的原始数据列表,其中可能包含相同主键(如 productId)但不同属性值的重复项。此时,简单调用 distinct() 无法满足需求——因为它依赖对象整体相等性;而我们需要的是按业务字段分组后,基于特定规则(如 productTitle 字典序升序)选择“最优”首项。
以如下 Product 类为例:
@Data
public class Product {
private long productId;
private String productTitle;
private String productDescription;
}给定原始数据: | ProductId | ProductTitle | ProductDescription | |-----------|--------------|------------------------| | 1 | car | used for driving | | 2 | book | reading books | | 3 | furniture | home decorations | | 3 | TV | TV entertainment | | 4 | jewelry | fashion item |
目标:对 productId = 3 的两条记录,保留 productTitle 字典序更小的 "furniture",剔除 "TV"(因 "furniture"
✅ 推荐解法:Stream 分组 + 条件聚合
核心思路分两步:
立即学习“Java免费学习笔记(深入)”;
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 按 productId 分组,并在每组内使用 Collectors.maxBy(...) 或 minBy(...) 选取满足排序条件的唯一元素;
- 展开结果并过滤空值,构建最终去重列表。
⚠️ 注意:maxBy(Comparator.comparing(Product::getProductTitle)) 实际返回字典序 最大 的项。本例需保留 最小 项,因此应使用 minBy —— 这是原答案中的关键疏漏,已修正。
✅ 正确代码如下:
import java.util.*;
import java.util.stream.Collectors;
List<Product> filteredProducts = products.stream()
.collect(Collectors.groupingBy(
Product::getProductId,
Collectors.minBy(Comparator.comparing(Product::getProductTitle))
))
.values() // Collection<Optional<Product>>
.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.toList();执行后输出:
Product(productId=1, productTitle=car, productDescription=used for driving) Product(productId=2, productTitle=book, productDescription=reading books) Product(productId=3, productTitle=furniture, productDescription=home decorations) Product(productId=4, productTitle=jewelry, productDescription=fashion item)
? 关键细节说明
- Collectors.minBy(...) 返回 Optional
,天然规避空指针风险; - 若某组无元素(理论上不会发生),Optional.empty() 会被后续 filter(Optional::isPresent) 安全跳过;
- 使用 toList()(Java 16+)确保返回不可变列表;若需可变列表,替换为 new ArrayList(...);
- 排序依据可灵活切换:例如按 productDescription 长度取最短项 → Comparator.comparing(p -> p.getProductDescription().length());
- 如需保留 首次出现 的元素(而非字典序最小),应改用 Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0)),但性能略低。
✅ 总结
该方案具备三大优势:
? 语义清晰:通过 groupingBy + minBy 直观表达“每组取最优首项”;
? 函数式安全:全程无显式循环与临时变量,避免并发与状态污染风险;
? 高度可扩展:仅需调整 Comparator 或下游收集器,即可适配任意去重策略(时间最早、价格最低、权重最高等)。
对于中大型项目,建议将此逻辑封装为通用工具方法,例如:
public static <T, K> List<T> distinctByFirstOfGroup(
Collection<T> items,
Function<T, K> classifier,
Comparator<T> selector) {
return items.stream()
.collect(Collectors.groupingBy(
classifier,
Collectors.minBy(selector)
))
.values()
.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
}
// 调用:distinctByFirstOfGroup(products, Product::getProductId,
// Comparator.comparing(Product::getProductTitle));

















