java浅拷贝和深拷贝

时间:2022-04-17 19:49:07

原文链接

1、什么是浅拷贝

   浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

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("Original Object: " + stud.getName() + " - " + stud.getSubj().getName());
// 拷贝对象
Student clonedStud = (Student) stud.clone();
System.out.println("Cloned Object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
// 原始对象和拷贝对象是否一样:
System.out.println("Is Original Object the same with Cloned Object: " + (stud == clonedStud));
// 原始对象和拷贝对象的name属性是否一样
System.out.println("Is Original Object's field name the same with Cloned Object: " +
(stud.getName() == clonedStud.getName()));
// 原始对象和拷贝对象的subj属性是否一样
System.out.println("Is Original Object's field subj the same with Cloned Object: " +
(stud.getSubj() == clonedStud.getSubj()));
stud.setName("Dan");
stud.getSubj().setName("Physics");
System.out.println("Original Object after it is updated: " + stud.getName() + " - " +
stud.getSubj().getName());
System.out.println("Cloned Object after updating original object: " + clonedStud.getName() +
" - " + clonedStud.getSubj().getName());
}
}
输出结果如下: 
 Original Object: John - Algebra
 Cloned Object: John - Algebra
 Is Original Object the same with Cloned Object: false
 Is Original Object's field name the same with Cloned Object: true
 Is Original Object's field subj the same with Cloned Object: true
 Original Object after it is updated: Dan - Physics
 Cloned Object after updating original object: John - Physics

     在这个例子中,我让要拷贝的类Student实现了Clonable接口并重写Object类的clone()方法,然后在方法内部调用super.clone()方法。从输出结果中我们可以看到,对原始对象stud的"name"属性所做的改变并没有影响到拷贝对象clonedStud,但是对引用对象subj的"name"属性所做的改变影响到了拷贝对象clonedStud。

2、什么是深拷贝

       深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

实现一:

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() {
// 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
Student s = new Student(name, subj.getName());
return s;
}
}
实现二: 通过序列化实现深拷贝

也可以通过序列化来实现深拷贝。序列化是干什么的?它将整个对象图写入到一个持久化存储文件中并且当需要的时候把它读取回来, 这意味着当你需要把它读取回来时你需要整个对象图的一个拷贝。这就是当你深拷贝一个对象时真正需要的东西。请注意,当你通过序列化进行深拷贝时,必须确保对象图中所有类都是可序列化的。

public class ColoredCircle implements Serializable { 
private int x;
private int y;

public ColoredCircle(int x, int y) {
this.x = x;
this.y = y;
}

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}

public int getY() {
return y;
}

public void setY(int y) {
this.y = y;
}

@Override
public String toString() {
return "x=" + x + ", y=" + y;
}
}
public class DeepCopy {

public static void main(String[] args) throws IOException {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;

try {
// 创建原始的可序列化对象
ColoredCircle c1 = new ColoredCircle(100, 100);
System.out.println("Original = " + c1);

ColoredCircle c2 = null;

// 通过序列化实现深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
// 序列化以及传递这个对象
oos.writeObject(c1);
oos.flush();
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bin);
// 返回新的对象
c2 = (ColoredCircle) ois.readObject();

// 校验内容是否相同
System.out.println("Copied = " + c2);
// 改变原始对象的内容
c1.setX(200);
c1.setY(200);
// 查看每一个现在的内容
System.out.println("Original = " + c1);
System.out.println("Copied = " + c2);
} catch (Exception e) {
System.out.println("Exception in main = " + e);
} finally {
oos.close();
ois.close();
}
}
}

这里,你只需要做以下几件事儿:
(1)确保对象图中的所有类都是可序列化的
(2)创建输入输出流
(3)使用这个输入输出流来创建对象输入和对象输出流
(4)将你想要拷贝的对象传递给对象输出流
(5)从对象输入流中读取新的对象并且转换回你所发送的对象的类

    在这个例子中,我创建了一个ColoredCircle对象c1然后将它序列化 (将它写到ByteArrayOutputStream中). 然后我反序列化这个序列化后的对象并将它保存到c2中。随后我修改了原始对象c1。然后结果如你所见,c1不同于c2,对c1所做的任何修改都不会影响c2。

注意,序列化这种方式有其自身的限制和问题:
因为无法序列化transient变量, 使用这种方法将无法拷贝transient变量。

   再就是性能问题。创建一个socket, 序列化一个对象, 通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。所以在性能上会有天壤之别。如果性能对你的代码来说是至关重要的,建议不要使用这种方式。它比通过实现Clonable接口这种方式来进行深拷贝几乎多花100倍的时间。