如何用Java8 Stream从两个Student列表提取目标元素并计算薪资差
实现方案
核心筛选规则
最终结果集需要覆盖两类元素:
- 列表A中存在,但在列表B中按
no+age+city三个字段完全匹配不到的对象 - 列表A和B能按上述三个字段匹配到,但薪资值不相等的对象;这类对象需要保留A侧的基础字段,将薪资值替换为「A的薪资 - B的薪资」的差值;三字段匹配但薪资相等的对象直接排除。
现有代码的逻辑缺陷
当前代码通过anyMatch筛选的规则是「只要A的元素能在B中按三个字段匹配到就保留」,完全没有覆盖两类筛选场景:
- 没有保留A存在但B完全匹配不到的元素
- 没有处理「三字段匹配但薪资不同时,替换为薪资差值」的逻辑
如果直接把anyMatch换成noneMatch,又会漏掉「三字段匹配但薪资不同」的第二类元素,筛选范围覆盖不全。
另外逐元素遍历A时全量扫描B列表做匹配的写法,时间复杂度为O(n*m),数据量较大时性能表现很差。
可落地的Java8 Stream实现
为了避免重复扫描B列表,先提前把B列表按匹配字段做索引映射,后续查找时可以直接通过键值匹配,时间复杂度优化为O(n+m):
// 1. 构建B列表的匹配索引:key为no+age+city拼接的唯一匹配键,value为对应的B侧Student对象 Map<String, Student> bIndex = B.stream() .collect(Collectors.toMap( stu -> buildMatchKey(stu.getNo(), stu.getAge(), stu.getCity()), Function.identity(), // 若B中存在同匹配键的重复元素,默认保留第一个出现的对象 (oldStu, newStu) -> oldStu )); // 2. 遍历A列表筛选符合要求的结果 List<Student> resultSet = A.stream() .map(aStu -> { String matchKey = buildMatchKey(aStu.getNo(), aStu.getAge(), aStu.getCity()); Student matchedBStu = bIndex.get(matchKey); // 场景1:B中无匹配项,直接返回原A对象 if (matchedBStu == null) { return aStu; } // 场景2:B中有匹配项,对比薪资值 // 薪资相等则不符合要求,返回null后续统一过滤 if (aStu.getSalary().equals(matchedBStu.getSalary())) { return null; } // 薪资不等则构造新对象,替换薪资为差值 return new Student( aStu.getNo(), aStu.getAge(), aStu.getCity(), aStu.getBirthDate(), aStu.getSalary() - matchedBStu.getSalary() ); }) // 过滤掉薪资相等返回null的无效元素 .filter(Objects::nonNull) .collect(Collectors.toList()); // 抽取的匹配键构造方法 private static String buildMatchKey(String no, Integer age, String city) { return no + "_" + age + "_" + city; }
效果验证
代入你给出的测试数据走查逻辑:
- A中no=2的Dublin学生、no=5的London学生:在B的匹配索引中找不到对应键值,直接纳入结果集
- A中no=1的Rome学生:匹配到B中同三字段的对象,A薪资300、B薪资700,计算得差值-400,纳入结果集
- A中no=3的Istanbul学生:匹配到B中同三字段的对象,两边薪资均为350,直接过滤不纳入
- A中no=4的Madrid学生:匹配到B中同三字段的对象,两边薪资均为450,直接过滤不纳入
最终输出的结果集和你给出的示例完全一致。
注意:如果数据量极小不想提前建索引,也可以直接在流中嵌套两层匹配实现,但不推荐,嵌套扫描的写法性能随列表长度增长下降很快。
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

