Java中对象复制后如何实现对象隔离?
解决BeanUtils拷贝后对象引用不隔离的问题
你遇到的这个问题本质是浅拷贝导致的。BeanUtils.copyProperties默认只做浅拷贝:对于基本数据类型(比如String、int这些),它会复制值;但对于像C这样的引用类型,它只是把a里AttC的内存地址复制给了b的AttC——也就是说,a.AttC和b.AttC指向同一个对象实例。所以你修改a.setAttC(null)时,b的AttC自然也变成了null,因为它们本来就是同一个东西。
要实现两个对象完全隔离,你需要做深拷贝:不仅复制基本类型的属性,还要为所有引用类型的属性创建新的实例,复制它们的内部属性。下面是几种可行的方案:
1. 手动实现深拷贝方法
这是最直观的方式,针对你的引用类型(比如C类)写一个拷贝方法,然后在复制对象时手动处理引用属性:
// 先给C类添加一个拷贝方法,复制自身的所有属性 public class C { private String name; // getter和setter省略 public C copy() { C newC = new C(); newC.setName(this.name); // 如果C还有其他引用类型属性,也要递归调用它们的copy方法 return newC; } } // 复制a到b的逻辑 A a = new A(); C c = new C(); c.setName("c name"); a.setAtt1("toto"); a.setAtt2("toto"); a.setAttC(c); B b = new B(); // 先用BeanUtils拷贝基本类型属性 BeanUtils.copyProperties(a, b); // 手动深拷贝C类型的属性 if (a.getAttC() != null) { b.setAttC(a.getAttC().copy()); } // 现在修改a的AttC,不会影响b了 a.setAttC(null); System.out.println(b.getAttC().getName()); // 输出"c name"
2. 通过序列化实现通用深拷贝
如果你的所有类(A、B、C)都实现了Serializable接口,可以用序列化+反序列化的方式生成一个完全独立的对象,这种方式不需要手动写每个类的拷贝方法:
import java.io.*; public class DeepCopyUtil { @SuppressWarnings("unchecked") public static <T> T deepCopy(T obj) throws IOException, ClassNotFoundException { if (obj == null) return null; ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); T copiedObj = (T) ois.readObject(); ois.close(); return copiedObj; } } // 使用示例 B b = new B(); BeanUtils.copyProperties(a, b); // 替换AttC为深拷贝后的实例 b.setAttC(DeepCopyUtil.deepCopy(a.getAttC()));
3. 使用第三方库实现深拷贝
如果不想自己写代码,可以用成熟的第三方库来处理,比如:
- MapStruct:编译期生成映射代码,性能高,支持深拷贝,只需要定义映射接口即可
- Apache Commons Lang:
SerializationUtils.clone()方法,基于序列化实现深拷贝 - ModelMapper:支持自动映射和深拷贝配置
以MapStruct为例,步骤如下:
- 引入MapStruct依赖(Maven/Gradle)
- 定义映射接口:
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @Mapper public interface AToBMapper { AToBMapper INSTANCE = Mappers.getMapper(AToBMapper.class); // MapStruct会自动处理基本类型和引用类型的深拷贝(只要C类的属性可以直接映射) B convert(A a); }
- 使用映射器:
B b = AToBMapper.INSTANCE.convert(a); // 修改a的AttC不会影响b a.setAttC(null); System.out.println(b.getAttC().getName()); // 输出"c name"
核心思路就是:让a和b的引用类型属性指向不同的对象实例,而不是同一个内存地址,这样修改其中一个就不会影响另一个了。
内容的提问来源于stack exchange,提问作者Victor
相关产品推荐
相关产品推荐

