Java8中基于nbr字段合并对象列表的重复值异常问题
Java 8 基于nbr字段合并两个列表时重复值问题解决
问题场景
有两个从BigQuery查询得到的Data DTO列表:
BQ结果列表1(bq1)
List<Data> bq1 = { [name:"abc", country: "ca", nbr:334], [name:"dcc", country: "ca", nbr:222], [name:"aop", country: "in", nbr:234] }
BQ结果列表2(bq2)
List<Data> bq2 = { [color:a, nbr:334], [color:n, nbr:334], [color:e, nbr:234] }
期望基于nbr字段合并两个列表,得到如下结果:
List<Data> result = { [name:"aop", country: "in", nbr:234, color: e], [name:"abc", country: "ca", nbr:334, color: a], [name:"abc", country: "ca", nbr:334, color: n] }
但实际得到错误结果,同nbr的记录出现重复的color值:
List<Data> result = { [name:"aop", country: "in", nbr:234, color: e], [name:"abc", country: "ca", nbr:334, color: a], [name:"abc", country: "ca", nbr:334, color: a] }
当前使用Java 8的实现代码:
List<Data> resultList = new ArrayList<>(); Map<String, List<Data>> mapByItemNumber = bqList.stream() .collect(Collectors.groupingBy(Data::getNbr)); for (Data item : bq3List) { if (mapByItemNumber.containsKey(item.getNbr())) { for (Data i : mapByItemNumber.get(item.getNbr())) { i.setColor(item.getColor()); resultList.add(i); } } }
问题原因
代码直接修改了mapByItemNumber中存储的原Data对象实例,并将同一个实例多次添加到结果列表:
- 处理
bq2中第一个nbr=334的条目(color:a)时,修改了bq1中name="abc"的Data对象的color为a,并添加到结果列表; - 处理
bq2中第二个nbr=334的条目(color:n)时,再次修改同一个Data实例的color为n,此时结果列表中已存在的该实例也会被同步修改; - 若后续操作或打印时出现异常,会导致最终结果出现重复的错误值。
解决方案
创建新的Data对象实例进行合并,避免修改原对象和重复引用:
方案1:使用构造函数复制属性
假设Data类有包含所有属性的构造函数:
List<Data> resultList = new ArrayList<>(); // 注意:确保getNbr的返回类型与map的key类型一致,若nbr是Integer则用Map<Integer, List<Data>> Map<Object, List<Data>> mapByItemNumber = bq1.stream() .collect(Collectors.groupingBy(Data::getNbr)); for (Data item : bq2) { List<Data> matchedItems = mapByItemNumber.get(item.getNbr()); if (matchedItems != null) { for (Data original : matchedItems) { // 创建新的Data对象,复制原属性并设置color Data merged = new Data(original.getName(), original.getCountry(), original.getNbr(), item.getColor()); resultList.add(merged); } } }
方案2:使用Builder模式
如果Data类有Builder实现:
List<Data> resultList = new ArrayList<>(); Map<Object, List<Data>> mapByItemNumber = bq1.stream() .collect(Collectors.groupingBy(Data::getNbr)); for (Data item : bq2) { List<Data> matchedItems = mapByItemNumber.get(item.getNbr()); if (matchedItems != null) { for (Data original : matchedItems) { Data merged = Data.builder() .name(original.getName()) .country(original.getCountry()) .nbr(original.getNbr()) .color(item.getColor()) .build(); resultList.add(merged); } } }
方案3:使用Stream API简化实现
Map<Object, List<Data>> mapByItemNumber = bq1.stream() .collect(Collectors.groupingBy(Data::getNbr)); List<Data> resultList = bq2.stream() .flatMap(item -> mapByItemNumber.getOrDefault(item.getNbr(), Collections.emptyList()) .stream() .map(original -> { // 手动复制属性创建新对象 Data merged = new Data(); merged.setName(original.getName()); merged.setCountry(original.getCountry()); merged.setNbr(original.getNbr()); merged.setColor(item.getColor()); return merged; })) .collect(Collectors.toList());
关键注意点
- 确保
mapByItemNumber的key类型与item.getNbr()的返回类型一致(比如都是Integer或String),避免因类型不匹配导致的匹配失败; - 始终通过创建新对象来存储合并结果,避免修改原列表中的对象,防止引发其他未知问题;
- 若
Data类是不可变类(没有setter方法),则必须通过构造函数或Builder来创建新实例。
内容的提问来源于stack exchange,提问作者neha menezes
相关产品推荐
相关产品推荐

