如何合并含List<XYZ>成员的Java对象A?实现去重与覆盖
嘿,这个问题我之前做项目的时候刚好碰到过,给你分享几种实用的实现思路,完全贴合你的需求~
实现思路与方案
首先得明确核心需求:
- 基础字段(
id、response):若两个对象的字段内容不同,用新对象(或指定优先级的对象)的值覆盖;相同则覆盖也没变化。 - 集合字段(
slots):合并两个集合为无重复元素的并集,同时如果XYZ内部还有嵌套集合,也要考虑递归合并的场景。
1. 手动实现合并(最灵活可控)
这种方式能完全按照你的业务逻辑定制,适合复杂场景。
第一步:定义XYZ的“重复”规则
要对slots去重,必须先让XYZ类能判断两个实例是否为“重复元素”。通常我们会基于业务唯一标识重写equals()和hashCode()方法,比如假设XYZ有一个唯一ID字段xyzId:
class XYZ { private String xyzId; // 其他字段 + 嵌套集合(比如List<SomeOtherClass> nestedList) // 重写equals和hashCode,基于xyzId判断唯一性 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; XYZ xyz = (XYZ) o; return Objects.equals(xyzId, xyz.xyzId); } @Override public int hashCode() { return Objects.hash(xyzId); } // getter、setter、构造器省略 }
第二步:实现A类的合并方法
可以在A类里写两种合并方式:一种是修改原对象,另一种是返回新的合并对象(推荐,避免副作用)。
方式一:返回新的合并对象(无副作用)
class A { String id; String response; List<XYZ> slots; // 将当前对象与another合并,返回全新的A对象 public A mergeIntoNew(A another) { A merged = new A(); // 初始化基础字段为当前对象的值 merged.id = this.id; merged.response = this.response; merged.slots = this.slots != null ? new ArrayList<>(this.slots) : new ArrayList<>(); if (another == null) { return merged; } // 覆盖基础字段:如果another的字段非空,就替换当前值(符合“不同则更新”) merged.id = Optional.ofNullable(another.id).orElse(merged.id); merged.response = Optional.ofNullable(another.response).orElse(merged.response); // 合并slots并去重 if (another.slots != null) { Set<XYZ> slotSet = new HashSet<>(merged.slots); slotSet.addAll(another.slots); merged.slots = new ArrayList<>(slotSet); } return merged; } // getter、setter、构造器省略 }
方式二:处理XYZ内部的嵌套集合合并
如果你的XYZ类里还有嵌套集合,需要递归合并内部内容(比如两个相同xyzId的XYZ,要合并它们的内部集合),可以调整合并逻辑:
public A mergeIntoNew(A another) { A merged = new A(); // 基础字段初始化... if (another == null) { return merged; } // 覆盖基础字段... // 处理slots:不仅去重,还要合并相同XYZ的内部集合 if (another.slots != null) { // 把当前slots转成Map,方便快速查找相同xyzId的元素 Map<String, XYZ> currentSlotMap = merged.slots.stream() .collect(Collectors.toMap(XYZ::getXyzId, xyz -> xyz)); for (XYZ anotherXyz : another.slots) { String xyzId = anotherXyz.getXyzId(); if (currentSlotMap.containsKey(xyzId)) { // 已存在相同XYZ,递归合并内部字段和集合 currentSlotMap.get(xyzId).merge(anotherXyz); } else { // 不存在,直接添加 currentSlotMap.put(xyzId, anotherXyz); } } // 把Map转回List merged.slots = new ArrayList<>(currentSlotMap.values()); } return merged; }
对应的,XYZ类也要添加merge方法:
public XYZ merge(XYZ another) { if (another == null) return this; // 合并内部嵌套集合(示例) if (another.nestedList != null) { if (this.nestedList == null) { this.nestedList = new ArrayList<>(); } Set<SomeOtherClass> nestedSet = new HashSet<>(this.nestedList); nestedSet.addAll(another.nestedList); this.nestedList = new ArrayList<>(nestedSet); } // 其他字段的合并逻辑... return this; }
2. 借助工具类简化基础字段处理
如果你的类有很多基础字段,手动复制太麻烦,可以用工具类(比如Apache Commons BeanUtils)快速处理基础字段的覆盖,再手动处理集合:
// 示例:用BeanUtils复制基础字段,然后处理集合 A merged = new A(); // 先复制obj1的字段 BeanUtils.copyProperties(merged, obj1); // 再用obj2的字段覆盖,符合“不同则更新” BeanUtils.copyProperties(merged, obj2); // 手动合并slots并去重 Set<XYZ> slotSet = new HashSet<>(); if (obj1.getSlots() != null) slotSet.addAll(obj1.getSlots()); if (obj2.getSlots() != null) slotSet.addAll(obj2.getSlots()); merged.setSlots(new ArrayList<>(slotSet));
注意:BeanUtils默认会用null值覆盖原有值,如果不想让null覆盖,需要自定义转换器。
3. 用MapStruct生成合并代码(适合多类场景)
如果你的项目中有很多类需要合并逻辑,用MapStruct可以在编译期生成高效的映射代码,减少重复工作:
@Mapper public interface AMapper { AMapper INSTANCE = Mappers.getMapper(AMapper.class); // 映射基础字段,忽略slots(手动处理) @Mapping(target = "slots", ignore = true) A mergeBaseFields(A target, A source); // 自定义合并逻辑 default A merge(A obj1, A obj2) { A merged = mergeBaseFields(obj1, obj2); // 合并slots并去重 Set<XYZ> slotSet = new HashSet<>(); if (obj1.getSlots() != null) slotSet.addAll(obj1.getSlots()); if (obj2.getSlots() != null) slotSet.addAll(obj2.getSlots()); merged.setSlots(new ArrayList<>(slotSet)); return merged; } }
使用时直接调用:
A merged = AMapper.INSTANCE.merge(obj1, obj2);
总结
- 简单场景:手动实现
merge方法,完全可控; - 多类合并场景:用MapStruct减少重复代码;
- 快速处理基础字段:结合BeanUtils+手动集合处理;
- 核心前提:必须明确“重复元素”的定义(重写
equals/hashCode或用唯一ID映射),否则无法正确去重。
内容的提问来源于stack exchange,提问作者MiGo




