Java 浅copy 和深copy
1. 前言
为什么要写这个文章是在于,今天翻到之前写到文章copy问题的时候没有解析原理,在这里补上。现象可以查看之前文章Java Bean Copy问题,可以从这个问题进而引发深copy和浅copy的问题。
2. 现象解析
我们知道Java虚拟机的内存结构包括虚拟机栈,本地方法栈,堆,方法区,计数器等。而创建对象所需要的是虚拟机栈,堆和方法区。
- 2.1 对象创建
当创建对象的时候会在堆中存放具体的数据和指向方法区中类型数据的指针,而在虚拟机栈中存放的是对象的引用。这里不讨论句柄和直接指向指针的区别。 - 2.2 Object#clone() clone方法解析
clone方法是Object 的方法,所有的类都会继承Object,即为所有方法创建的对象都可以使用super.clone()方法。而clone方法是浅copy。
3. 验证clone()方法复制的范围
此处定义一个对象User,类信息如下:
@Setter
@Getter
@ToString
public class Person implements Cloneable{
private String id ;
private Object explain;
private String name ;
private List<Child> childList;
private int[] scores;
private Sex sex;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
// 枚举 Sex
public enum Sex {
MALE, FEMALE;
}
@Data
public class Child implements Cloneable {
private String name;
private Sex sex;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试代码如下:
Person p = new Person();
p.setId("1");
p.setName("zhangsan");
p.setExplain(Arrays.asList("1234444"));
int[] scores = new int[]{88,77,78,99};
p.setScores(scores);
List<Child> childList = new ArrayList<>();
Child child = new Child();
child.setName("lihua");
child.setSex(Sex.FEMALE);
childList.add(child);
p.setChildList(childList);
p.setSex(Sex.MALE);
Person clone = (Person) p.clone();
System.out.println("person == " + p);
// 修改
clone.setSex(Sex.FEMALE);
clone.setId("123");
clone.setName("lisi");
clone.setExplain("12323");
int[] scores1 = clone.getScores();![](https://img-blog.csdnimg.cn/20210408001345431.png)
scores1[0] = 999;
clone.setScores(scores1);
List<Child> cloneChildList = clone.getChildList();
for (Child child1 : cloneChildList) {
child1.setName("child");
child1.setSex(Sex.MALE);
}
clone.setChildList(cloneChildList);
System.out.println("person == " + p);
System.out.println("clone == " + clone);
执行结果:
结果分析:
- 属性 基础类型,String 没有发生改变。
- 集合和自定义引用类型影响了原对象。
- Object没有发生影响。
- 数组内部变更发生了影响。
- 枚举类型没有变化。
总结:clone 后的变更对象引用同一个对象,发生改变的时候才会更改,也就是说clone是浅copy。
4. 重写clone()方法和实现deepClone()方法
重新有两种方式:
- 接对无法clone的引用类型再次clone即可。
例如(根据具体业务调整clone方法):
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
List<Child> children = new ArrayList<>();
List<Child> childList = person.getChildList();
for (Child child : childList) {
Child clone = (Child) child.clone();
children.add(clone);
}
person.setChildList(children);
int[] clone = person.getScores().clone();
person.setScores(clone);
return person;
}
- 对对象进行序列化和反序列化
可以通过反序列化和反序列化来实现深copy。可以写入一个文件,也可以直接通过字节流实现。
deepClone方法实现:
public Object deepClone() throws IOException, ClassNotFoundException {
// 字节流处理
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
在测试的时候会抛出序列化的异常,记得将User和Child实现 Serializable 进行序列化。
3. 第三方工具实现
可以使用第三方Jar包实现深copy,例如 Spring 的 BeanUtils.copyProperties(Object source, Object target) throws BeansException 方法。
嗯,补充一句尽量避免使用clone方法。