如何用Java 8 Stream按ProductId分组并获取最大ProductTransactionId
问题需求
我有一个ProductTransaction列表,需要找出每个ProductId对应的最大(最新)productTransactionId的交易记录。要求按ProductId分组,依据productTransactionId排序,示例最终结果为List<Integer> (2, 5, 9)。当前使用Java 8,想用Stream和Filter实现,现有方案比较冗长,希望得到更简洁或性能更优的写法。
实体类定义
@Data public class ProductTransaction { private int productTransactionId; private int productId; private Date saleDate; private BigDecimal amount; }
示例数据
| ProductTransactionId | ProductId | SaleDate | Amount |
|---|---|---|---|
| 1 | 1 | 3/2/2019 | 5 |
| 2 | 1 | 4/1/2019 | 9 |
| 3 | 2 | 4/1/2019 | 2 |
| 4 | 2 | 8/21/2019 | 3 |
| 5 | 2 | 8/21/2019 | 4 |
| 6 | 3 | 10/1/2019 | 2 |
| 7 | 3 | 10/3/2019 | 5 |
| 8 | 3 | 10/3/2019 | 7 |
| 9 | 3 | 10/3/2019 | 8 |
注意:忽略SaleDate字段,仅按ProductTransactionId排序;输入数据不一定有序。
现有冗长实现
Set<Long> finalProductTransactionIds = new HashSet<>(); Set<Long> distinctProductIds = productTransactions.stream() .map(ProductTransaction::getProductId) .collect(Collectors.toSet()); for (Long productId: distinctProductIds) { Long productTransactionId = productTransactions.stream() .filter(x -> x.getProductId() == productId) .sorted(Comparator.comparing(ProductTransaction::getProductTransactionId) .reversed()) .collect(Collectors.toList()).get(0).getProductTransactionId(); finalProductTransactionIds.add(productTransactionId); }
优化方案
方案1:分组取最大值(直观清晰)
通过一次流操作完成分组和最大值筛选,避免多次遍历列表:
List<Integer> maxTransactionIds = productTransactions.stream() // 按ProductId分组,每组保留transactionId最大的记录 .collect(Collectors.groupingBy( ProductTransaction::getProductId, Collectors.maxBy(Comparator.comparingInt(ProductTransaction::getProductTransactionId)) )) .values() .stream() .filter(Optional::isPresent) .map(Optional::get) .map(ProductTransaction::getProductTransactionId) .collect(Collectors.toList());
优势:
- 仅遍历原始列表1次,性能远优于原方案(原方案遍历次数=1+不同ProductId数量)
- 代码逻辑清晰,符合Stream API链式风格
方案2:直接映射去重合并(最简洁高效)
如果只需要最大交易ID集合,用toMap的合并函数直接生成结果:
// 若需要List则用 new ArrayList<>(...) 包裹结果 Set<Integer> maxTransactionIds = productTransactions.stream() .collect(Collectors.toMap( ProductTransaction::getProductId, ProductTransaction::getProductTransactionId, // 同一ProductId下保留较大的transactionId (existing, newVal) -> Math.max(existing, newVal) )) .values();
优势:
- 代码最紧凑,无多余中间操作
- 同样仅遍历1次列表,性能最优
- 结果自动去重,直接得到每个ProductId对应最大交易ID
内容的提问来源于stack exchange,提问作者mattsmith5
相关产品推荐
相关产品推荐

