转载:http://blog.youkuaiyun.com/wangnanwlw/article/details/52300117
一、引言
对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部 数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。
二、浅拷贝
1、什么是浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
在图中,SourceObject有一个int类型的属性 “field1”和一个引用类型属性”refObj”(引用ContainedObject类型的对象)。当对SourceObject做浅拷贝时,创建了CopiedObject,它有一个包含”field1”拷贝值的属性”field2”以及仍指向refObj本身的引用。由于”field1”是基本类型,所以只是将它的值拷贝给”field2”,但是由于”refObj”是一个引用类型, 所以CopiedObject指向”refObj”相同的地址。因此对SourceObject中的”refObj”所做的任何改变都会影响到CopiedObject。2、如何实现浅拷贝
下面是实现浅拷贝的一个例子
public class Subject {
private String name;
public Subject(String s) {
name = s;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
}
public class Student implements Cloneable {
// 对象引用
private Subject subj;
private String name;
public Student(String s, String sub) {
name = s;
subj = new Subject(sub);
}
public Subject getSubj() {
return subj;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
/**
* 重写clone()方法
* @return
*/
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class CopyTest {
public static void main(String[] args) {
// 原始对象
Student stud = new Student("John", "Algebra");
System.out.println("原始对象: " + stud.getName() + " - " + stud.getSubj().getName());
// 拷贝对象
Student clonedStud = (Student) stud.clone();
System.out.println("拷贝对象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
// 原始对象和拷贝对象是否一样:
System.out.println("原始对象和拷贝对象是否一样: " + (stud == clonedStud));
// 原始对象和拷贝对象的name属性是否一样
System.out.println("原始对象和拷贝对象的name属性是否一样: " + (stud.getName() == clonedStud.getName()));
// 原始对象和拷贝对象的subj属性是否一样
System.out.println("原始对象和拷贝对象的subj属性是否一样: " + (stud.getSubj() == clonedStud.getSubj()));
stud.setName("Dan");
stud.getSubj().setName("Physics");
System.out.println("原始对象更新: " + stud.getName() + " - " + stud.getSubj().getName());
System.out.println("原始对象更新后,浅复制对象变化: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
}
}
打印结果:
原始对象: John - Algebra
拷贝对象: John - Algebra
原始对象和拷贝对象是否一样: false
原始对象和拷贝对象的name属性是否一样: true
原始对象和拷贝对象的subj属性是否一样: true
原始对象更新: Dan - Physics
原始对象更新后,浅复制对象变化: John - Physics
在这个例子中,我让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。从输出结果中我们可以看到,对原始对象stud的”name”属性所做的改变并没有影响到拷贝对象clonedStud,但是对引用对象subj的”name”属性所做的改变影响到了拷贝对象clonedStud。
三、深拷贝
- 1、什么是深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
在上图中,SourceObject有一个int类型的属性 “field1”和一个引用类型属性”refObj1”(引用ContainedObject类型的对象)。当对SourceObject做深拷贝时,创建了CopiedObject,它有一个包含”field1”拷贝值的属性”field2”以及包含”refObj1”拷贝值的引用类型属性”refObj2” 。因此对SourceObject中的”refObj”所做的任何改变都不会影响到CopiedObject
- 2、如何实现深拷贝
下面是实现深拷贝的一个例子。只是在浅拷贝的例子上做了一点小改动,Subject 和CopyTest 类都没有变化。
public class Subject {
private String name;
public Subject(String s) {
name = s;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
}
public class Student implements Cloneable {
// 对象引用
private Subject subj;
private String name;
public Student(String s, String sub) {
name = s;
subj = new Subject(sub);
}
public Subject getSubj() {
return subj;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
/**
* 重写clone()方法
* @return
*/
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class CopyTest {
public static void main(String[] args) {
// 原始对象
Student stud = new Student("John", "Algebra");
System.out.println("原始对象: " + stud.getName() + " - " + stud.getSubj().getName());
// 拷贝对象
Student clonedStud = (Student) stud.clone();
System.out.println("拷贝对象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
// 原始对象和拷贝对象是否一样:
System.out.println("原始对象和拷贝对象是否一样: " + (stud == clonedStud));
// 原始对象和拷贝对象的name属性是否一样
System.out.println("原始对象和拷贝对象的name属性是否一样: " + (stud.getName() == clonedStud.getName()));
// 原始对象和拷贝对象的subj属性是否一样
System.out.println("原始对象和拷贝对象的subj属性是否一样: " + (stud.getSubj() == clonedStud.getSubj()));
stud.setName("Dan");
stud.getSubj().setName("Physics");
System.out.println("原始对象更新: " + stud.getName() + " - " + stud.getSubj().getName());
System.out.println("原始对象更新后,浅复制对象变化: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
}
}
打印结果:
原始对象: John - Algebra
拷贝对象: John - Algebra
原始对象和拷贝对象是否一样: false
原始对象和拷贝对象的name属性是否一样: true
原始对象和拷贝对象的subj属性是否一样: false
原始对象更新: Dan - Physics
原始对象更新后,浅复制对象变化: John - Algebra
很容易发现clone()方法中的一点变化。因为它是深拷贝,所以你需要创建拷贝类的一个对象。因为在Student类中有对象引用,所以需要在Student类中实现Cloneable接口并且重写clone方法。
如何选择:
如果对象的属性全是基本类型的,那么可以使用浅拷贝,但是如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。我的意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。
通过序列化实现深拷贝:
也可以通过序列化来实现深拷贝。序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。
注意,序列化这种方式有其自身的限制和问题:
因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。
再就是性能问题。创建一个socket, 序列化一个对象, 通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。所以在性能上会有天壤之别。如果性能对你的代码来说是至关重要的,建议不要使用这种方式。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间。