浅拷贝、深拷贝的概念
在 Java 中,除了基本数据类型(元类型)之外,还存在“类的实例对象”这个引用数据类型。而一般使用“ = ”号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值;但是对于对象而言,赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
所谓的浅拷贝和深拷贝,是在拷贝对象的时候,对“类的实例对象”这种引用数据类型的不同操作。对于基本数据类型,赋值的就是值,不存在深拷贝、浅拷贝的问题。
浅拷贝的实现
Java中的clone()
clone()方法是一个本地方法,实现的是浅拷贝。调用该方法的前提如下。否则就会抛出错误。
- 实现java.lang.Cloneable接口
- 或)其某个父类实现Cloneable接口
- 或)实现了Cloneable的子接口,
clone()进行的是浅复制(shallow copy)。clone()会将被复制实例的字段值直接复制到新的实例中,并不考虑字段中实际所保存的内容。clone()在进行复制时也不会调用被复制实例的构造函数。在使用时要覆写clone(),可以通过覆写clone()来实现自己的copy逻辑。
clone()的执行过程
- 1.分配与要复制的实例同样大小的内存空间;
- 2.将要复制的实例中的字段的值复制到所分配的内存空间中去。
Cloneable是一个标记接口(marker interface),其并没有声明任何方法,只是用来标记 “可以使用clone方法进行复制”。
测试案例
定义两个类,父类的成员包括子类。
对比结果,发现赋值后的父类对象与原对象的hashcode不同,说明内存地址是不一样。而对于对象里的name和子类对象,hashcode是不同的,前者是字符串常量池,后者是符号引用的原因。拷贝的过程中,在新对象的内存地址中,对应的位置存储的是子类对象在堆中的地址。两个父类对象指向的相同的子类对象。这就是浅拷贝。
如何进行深拷贝
比较常用的方案有两种:
- 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象。
- 继续利用 clone() 方法,对其内的引用类型的变量,再进行一次 clone()。
利用clone()方法实现
改写子类,让它也实现clone()方法。
改写父类的clone()方法,对子类执行子类的clone()方法
再次执行,子类对象也拷贝了
分析:该案例对于子类而言进行了一次浅拷贝;对于父类而言进行了一次深拷贝。如果子类还有引用类型,可以继续这样做来实现。
使用序列化反序列化实现
public void main() throws IOException {
TestFatherClass father1 = new TestFatherClass();
father1.fatherAge = 50;
father1.fatherName = "一号";
father1.children = new TestChildrenClass();
father1.children.childrenName = "小一号";
father1.children.childrenAge = 20;
TestFatherClass father2;
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("XXX\\iotest.txt"));
oos.writeObject(father1);
oos.flush();
}catch (IOException e){
throw new RuntimeException(e);
}finally {
try {
oos.close();
}catch (IOException e){
throw new RuntimeException(e);
}
}
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("XXX\\iotest.txt"));
father2 = (TestFatherClass) ois.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}finally {
try {
ois.close();
}catch (IOException e) {
throw new RuntimeException(e);
}
}
System.out.print("1==2:" + (father1 == father2) + "\n");
System.out.print("1.hashcode:" + father1.hashCode() + "\n");
System.out.print("2.hashcode:" + father2.hashCode() + "\n");
System.out.print("----------" + "\n");
System.out.print("children1==children2:" + (father1.children == father2.children) + "\n");
System.out.print("children1.hashcode:" + father1.children.hashCode() + "\n");
System.out.print("children2.hashcode:" + father2.children.hashCode() + "\n");
}
输出为