场景
现在有一个Student对象,想要将这个对象拷贝一份,有三种拷贝方式:
- 直接赋值给另一个变量,这样内存地址完全没变,没有实现拷贝。
Student student = new Student();
Student student1 = student;
-
浅拷贝(省略其余代码),只对Student 对象实现Cloneable接口,并实现clone方法。这样的结果是,Student对象实现了拷贝,但是Student内的其他对象,比如Student内部有一个成员Person对象就没有实现拷贝。
-
深拷贝,就是我们通常想要的完全拷贝,不仅该对象Student实现拷贝,该对象内部Person对象同样也实现了拷贝。深拷贝通常有两种方法,一种一层一层的实现Cloneable接口,还有一种是序列化方法。两者相比较,前者繁琐,但效率高,后者简便但效率低。
具体实现+测试
两个对象代码(省略无关get/set方法):
Person对象
public class Person implements Cloneable, Serializable {
private String name;
private int age;
private Integer age1;
@Override
protected Person clone() {
try {
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
Student对象
public class Student implements Cloneable, Serializable {
private Person person;
private String className;
@Override
protected Student clone() {
try {
Student student = (Student) super.clone();
student.setPerson(this.getPerson().clone());
return student;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- 直接赋值
/*1. 初始化一些值*/
Person person = new Person("张三", 20, 21);
Student student = new Student(person, "三年二班");
/*2. 直接拷贝的情况*/
Student studentCopy1 = student;
System.out.println("student内存地址为:"+ student);
System.out.println("studentCopy1内存地址为:"+ studentCopy1);
直接拷贝打印结果为:
/*
* 内存地址测试
* 打印结果为:
* student内存地址为:copy.Student@1540e19d
* studentCopy1内存地址为:copy.Student@1540e19d(内存一致)
* 结论:内存地址一致,未起到拷贝作用
* */
- 浅拷贝
/*3. 浅拷贝测试*/
Student studentCopy2 = student.clone();
System.out.println("student内存地址为:"+ student);
System.out.println("studentCopy2内存地址为:"+ studentCopy2);
System.out.println("student中Person内存地址为:"+ student.getPerson());
System.out.println("studentCopy2中Person内存地址为:"+ studentCopy2.getPerson());
浅拷贝打印结果为:
/*
* 内存地址测试
* 打印结果:
* student内存地址为:copy.Student@1540e19d
* studentCopy2内存地址为:copy.Student@677327b6(内存不一致)
* student中Person内存地址为:copy.Person@14ae5a5
* studentCopy2中Person内存地址为:copy.Person@14ae5a5(内存一致)
* 结论:浅拷贝对于对象中的对象是拷贝不了的
* */
- 深拷贝
/*4. 深拷贝测试*/
long start = System.currentTimeMillis();
Student studentCopy3 = student.clone();
long end = System.currentTimeMillis();
System.out.println("student内存地址为:"+ student);
System.out.println("studentCopy3内存地址为:"+ studentCopy3);
System.out.println("student中Person内存地址为:"+ student.getPerson());
System.out.println("studentCopy3中Person内存地址为:"+ studentCopy3.getPerson());
System.out.println("耗时:" + (end - start) + "ms");
深拷贝打印结果为:
/*
* 内存地址测试
* 打印结果:
* student内存地址为:copy.Student@1540e19d
* studentCopy3内存地址为:copy.Student@6d6f6e28(内存不一致)
* student中Person内存地址为:copy.Person@14ae5a5
* studentCopy3中Person内存地址为:copy.Person@135fbaa4 (内存不一致)
* 耗时:0ms
* 结论:深拷贝确实可以做到所有对象拷贝,操作较为麻烦,但性能极高
* */
- 序列化实现深拷贝
/*5. 序列化实现深拷贝测试*/
start = System.currentTimeMillis();
Student studentCopy4 = clone(student); //序列化方法
end = System.currentTimeMillis();
System.out.println("student内存地址为:"+ student);
System.out.println("studentCopy4内存地址为:"+ studentCopy4);
System.out.println("student中Person内存地址为:"+ student.getPerson());
System.out.println("studentCopy4中Person内存地址为:"+ studentCopy4.getPerson());
System.out.println("耗时:" + (end - start) + "ms");
序列化实现深拷贝打印结果为:
/*
* 内存地址测试
* 打印结果:
* student内存地址为:copy.Student@1540e19d
* studentCopy4内存地址为:copy.Student@1d81eb93(内存不一致)
* student中Person内存地址为:copy.Person@14ae5a5
* studentCopy4中Person内存地址为:copy.Person@7291c18f(内存不一致)
* 耗时:85ms
* 结论:序列化实现深拷贝很方便,但是性能相比较前者慢很多
* */
序列化具体实现的方法,即下面的clone方法,具体代码为:
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;
}
结论
浅拷贝往往达不到我们要的效果,深拷贝才可以。
深拷贝有两种方式,无所谓耗时的采用实现Cloneable接口方式,考虑效率的采用序列化方式。