Java 的深拷贝和浅拷贝
什么是深拷贝、浅拷贝 (深克隆、浅克隆)?
在 Java 中,数据类型分为 基本数据类型 和 引用数据类型,深/浅拷贝是针对于 引用数据类型 来说的。
-
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝称为浅拷贝。通过实现Cloneable接口并重写Object类中的clone()方法可以实现浅克隆。
-
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容称为深拷贝。
如何进行深拷贝?
- 实现Cloneable接口并重写Object类及成员变量中引用类型的的clone()方法
- 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
注意:基于序列化和反序列化实现的拷贝不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
举个栗子
实现Cloneable接口 : 浅克隆 vs 深克隆
- 浅克隆
@Getter
@AllArgsConstructor
public class Sub {
private String sName;
}
@AllArgsConstructor
public class Parent implements Cloneable {
private String pName;
private Sub sub;
@Override
protected Object clone() {
Parent parent = null;
try {
parent = (Parent) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return parent;
}
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = (Parent) parent.clone();
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
输出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
991505714
sName
=================hashcode================
false
true
- 深克隆
@Getter
@AllArgsConstructor
public class Sub implements Cloneable {
private String sName;
@Override
protected Sub clone() {
Sub sub = null;
try {
sub = (Sub) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sub;
}
}
@AllArgsConstructor
public class Parent implements Cloneable {
private String pName;
private Sub sub;
@Override
protected Object clone() {
Parent parent = null;
try {
parent = (Parent) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
assert parent != null;
parent.sub = sub.clone();
return parent;
}
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = (Parent) parent.clone();
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
输出:
==============parent===================
166239592
pName
991505714
sName
================clone===================
385242642
pName
824009085
sName
=================hashcode================
false
false
实现Serializable接口,深克隆
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
@Getter
@AllArgsConstructor
public class Sub implements Serializable {
private String sName;
}
@AllArgsConstructor
public class Parent implements Serializable {
private String pName;
private Sub sub;
public static void main(String[] args) {
Sub sub = new Sub("sName");
Parent parent = new Parent("pName", sub);
Parent clone = CloneUtils.clone(parent);
System.out.println("==============parent===================");
System.out.println(parent.hashCode());
System.out.println(parent.pName);
System.out.println(parent.sub.hashCode());
System.out.println(parent.sub.getSName());
System.out.println("================clone===================");
System.out.println(clone.hashCode());
System.out.println(clone.pName);
System.out.println(clone.sub.hashCode());
System.out.println(clone.sub.getSName());
System.out.println("=================hashcode================");
System.out.println(parent.hashCode() == clone.hashCode());
System.out.println(parent.sub.hashCode() == clone.sub.hashCode());
}
}
输出:
==============parent===================
708049632
pName
716083600
sName
================clone===================
1791930789
pName
762152757
sName
=================hashcode================
false
false
扩展
- 标识接口 : Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。