如何在Java Stream中单次遍历获取某字段值最小的所有元素
单次遍历找出字段值最小的所有记录
针对你的需求,确实可以通过单次遍历实现,无需两次遍历或构建全部分组Map,下面提供两种优雅的Java实现方式:
方式一:自定义Collector(推荐,可读性高)
自定义Collector可以在遍历过程中维护当前的最小loanAmount以及对应的Person列表,逻辑清晰且支持并行流:
1. 定义辅助容器类
这个类负责处理单个元素、合并并行流的中间结果,并最终返回结果列表:
class MinLoanHolder { private BigDecimal minAmount; private List<Person> persons; private MinLoanHolder() { this.minAmount = null; this.persons = new ArrayList<>(); } // 处理单个Person对象 void accept(Person person) { BigDecimal currentLoan = person.loanAmount(); if (minAmount == null) { // 第一个元素,初始化最小金额和列表 minAmount = currentLoan; persons.add(person); } else { int compareResult = currentLoan.compareTo(minAmount); if (compareResult < 0) { // 找到更小的金额,重置列表 minAmount = currentLoan; persons.clear(); persons.add(person); } else if (compareResult == 0) { // 金额相等,添加到列表 persons.add(person); } // 金额更大,直接忽略 } } // 合并两个Holder(并行流场景下需要) MinLoanHolder combine(MinLoanHolder other) { if (this.minAmount == null) return other; if (other.minAmount == null) return this; int compareResult = this.minAmount.compareTo(other.minAmount); if (compareResult < 0) { return this; } else if (compareResult > 0) { return other; } else { // 金额相同,合并两个列表 this.persons.addAll(other.persons); return this; } } // 返回最终结果 List<Person> getResult() { return persons; } }
2. 创建并使用自定义Collector
// 定义Collector Collector<Person, ?, List<Person>> minLoanCollector = Collector.of( MinLoanHolder::new, MinLoanHolder::accept, MinLoanHolder::combine, MinLoanHolder::getResult ); // 假设persons是从API转换得到的Person流 List<Person> minLoanPersons = persons.stream() .collect(minLoanCollector);
方式二:使用Stream.reduce()(无需额外类)
如果不想定义辅助类,可以用reduce方法,通过数组临时存储当前的最小金额和对应列表:
// 假设persons是从API转换得到的Person流 List<Person> minLoanPersons = persons.stream() .reduce( // 初始值:[当前最小金额, 对应Person列表] new Object[]{null, new ArrayList<Person>()}, // 累积逻辑:处理每个Person并更新状态 (acc, person) -> { BigDecimal currentLoan = person.loanAmount(); BigDecimal minAmount = (BigDecimal) acc[0]; List<Person> list = (List<Person>) acc[1]; if (minAmount == null) { acc[0] = currentLoan; list.add(person); } else { int compareResult = currentLoan.compareTo(minAmount); if (compareResult < 0) { acc[0] = currentLoan; list.clear(); list.add(person); } else if (compareResult == 0) { list.add(person); } } return acc; }, // 合并逻辑:并行流时合并两个状态 (acc1, acc2) -> { BigDecimal min1 = (BigDecimal) acc1[0]; BigDecimal min2 = (BigDecimal) acc2[0]; List<Person> list1 = (List<Person>) acc1[1]; List<Person> list2 = (List<Person>) acc2[1]; if (min1 == null) return acc2; if (min2 == null) return acc1; int compareResult = min1.compareTo(min2); if (compareResult < 0) { return acc1; } else if (compareResult > 0) { return acc2; } else { list1.addAll(list2); return acc1; } } )[1]; // 取出列表部分
注意事项
- 比较
BigDecimal必须用compareTo方法,不要用equals,因为equals会同时比较值和精度(比如1.0和1.00会被判定为不相等),而业务上通常认为这两个金额是相同的。 - 两种方式都支持并行流,且全程只存储当前最小金额对应的记录,内存效率远高于
groupingBy方案。
内容的提问来源于stack exchange,提问作者Hrishikesh Joshi
相关产品推荐
相关产品推荐

