一、出现原因
在项目中经常需要复制一个完全一样的对象,然后再对新对象进行更新等操作而不影响老对象。
而以以下方式获取是否会出现问题呢?
User user = new User();
User copyUser = user;
答案是肯定的,上面的方法不能称之为复制对象,更准确地说应该是复制引用,因为user和copyUser指向的是内存堆里的同一个对象:
user ¦→ Object
copyUser ¦→ ↑
栈区(引用) ¦ 堆区(对象)
那么如何才能实现呢?应该使用Java中的拷贝(Object Copy),主要分为:浅拷贝 (Shallow Copy)、深拷贝 (Deep Copy),用的方法为clone()。
二、浅拷贝与深拷贝的区别
1、浅拷贝:
类实现默认的Object.clone()方法,拷贝对象时,
(1)对于引用类型的成员变量(属性)拷贝只是拷贝“值”即地址(引用),没有在堆中开辟新的内存空间;
(2)对于字段类型是值类型(基本类型)的,那么对该字段进行复制。
2、深拷贝:
类重写clone()方法,对于引用类型成员变量,重新在堆中开辟新的内存空间,简单地说,将对象的全部属性内容都拷贝一份新的。
3、使用:
如果复制对象只含基本数据类型,则使用浅拷贝即可满足;若果包含成员对象,则需使用深拷贝。
三、浅拷贝
一般实现方法:
- 被复制的类需要实现 Clonenable 接口(不实现的话在调用 clone 方法会抛出 CloneNotSupportedException 异常) 该接口为标记接口 (不含任何方法)
- 覆盖 clone () 方法,方法中调用 super.clone () 方法得到需要的复制对象,(native 为本地方法)
public class Person implements Cloneable{
private String name;
private int age;
private Address address;
//构造方法
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String display(){
return "Person[name="+name+",age="+age+",address"+address+"]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
public class Address {
private String province;//省份
private String city;//所在城市
//构造方法
public Address(String province, String city) {
this.province = province;
this.city = city;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address[province="+province+",city="+city+"]";
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person=new Person("张三",20,new Address("安徽","合肥"));
Person clonePerson=(Person) person.clone();
System.out.println(person);
//com.hand.hand.clone.Person@1d81eb93
System.out.println(clonePerson);
//com.hand.hand.clone.Person@1d81eb93
System.out.println(person.display());
//Person[name=张三,age=20,addressAdderss[provice=安徽,city=合肥]]
System.out.println(clonePerson.display());
//Person[name=张三,age=20,addressAdderss[provice=安徽,city=合肥]]
//证明String的特殊性,因为String是final的,所以会开辟新的内存空间
clonePerson.setName("李四");
//证明基本数据类型是被拷贝是开辟新空间的
clonePerson.setAge(22);
//证明成员变量所指向的是同一个对象
Address address=clonePerson.getAddress();
address.setProvince("江苏");
System.out.println(person.display());
//Person[name=张三,age=20,addressAdderss[provice=江苏,city=合肥]]
System.out.println(clonePerson.display());
//Person[name=李四,age=22,addressAdderss[provice=江苏,city=合肥]]
}
}
四、深拷贝
针对以上成员变量无法完成拷贝的情况出现了深拷贝。
实现方式有以下两种:
- 第一种
与通过重写 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;
}
}
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);
System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
//20jinan
System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
//10beijing
stu2.setAddress(address);
System.out.println(stu1.getAge() + stu1.getAddress().getAddress());
//20jinan
System.out.println(stu2.getAge() + stu2.getAddress().getAddress());
//10jinan
}
}