Java 中的对象拷贝 (Object Copy) 指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。举例说明:比如,对象 A 和对象 B 都属于类 S,具有属性 a 和 b。那么对对象 A 进行拷贝操作赋值给对象 B 就是:B.a=A.a; B.b=A.b;
在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部 数据。
Java 中的对象拷贝主要分为:浅拷贝 (Shallow Copy)、深拷贝 (Deep Copy)。
Student stu1 = new Student();
Student stu2 = stu1;
以上的代码,改变stu1或者stu2中任意一个对象的属性,另一个对象的属性也会随之改变。
原因出在 (stu2 = stu1) 这一句。该语句的作用是将 stu1 的引用赋值给 stu2,
这样,stu1 和 stu2 指向内存堆中同一个对象。
那么如何复制一个对象呢,答案就是Object类的clone方法
一、浅拷贝
一般步骤是(浅复制):
-
被复制的类需要实现 Clonenable 接口(不实现的话在调用 clone 方法会抛出 CloneNotSupportedException 异常) 该接口为标记接口 (不含任何方法)
-
覆盖 clone () 方法,访问修饰符设为 public。方法中调用 super.clone () 方法得到需要的复制对象,(native 为本地方法)
Student类
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
Student类实现Cloneable接口,并重写clone方法(重写只是简单调用父类的clone方法即可),对于类中只有基本数据类型的对象,浅复制即可解决问题。
但是对于那些类成员变量是对象的类,浅复制的方式就不行了,因为浅复制只是复制了对象的成员变量的引用,对于基本数据类型是创建新的值,但是对于引用数据类型的对象,这只是复制了引用,两者指向的还是同一个对象。
二、深拷贝
深拷贝的方式有多个
-
第一种
与通过重写 clone 方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现 Cloneable 接口并重写 clone 方法,最后在最顶层的类的重写的 clone 方法中调用所有的 clone 方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝 = 深拷贝。 -
第二种
结合序列化来解决这个问题,先把对象序列化,然后再反序列化成对象,该对象保证每个引用都是崭新的。这个就形成了多个引用,原引用和反序列化之后的引用不在相同,具体实现:
【序列化代码实现】
public class Student implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private int age;
private Address address;
public void setAddress(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@Override
protected Object clone() {
Student stu = null;
try {
// 将对象写成byte array
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 从流中读出byte array
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
stu = (Student) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return stu;
}
}
Student类需要实现Cloneable、Serializable两个接口,而且student中的成员变量对象都要实现serializable接口
public class Test {
public static void main(String[] args) {
Student stu1 = new Student();
Address address = new Address();
address.setAddress("beijing");
stu1.setAge(20);
stu1.setAddress(address);
Student stu2 = (Student) stu1.clone();
address.setAddress("jinan");
stu2.setAge(10);
//stu2.setAddress(address);
System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
}
}
输出:
20jinan
10beijing
注意,通过address.setAddress(“jinan”);修改了address的值,但是引用没有变化
所以stu1发生了变化,但是由于clone方法,stu2的address的引用已经发生变化了,所以stu2 的值没有发生变化,
若此时添加语句stu2.setAddress(address);
则输出结果为:
20jinan
10jinan
因为把stu2的引用又修改为跟stu1一致了,此时深拷贝就没意义了。