什么是克隆
克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝。
实现克隆有多种方式,可以手工的new出一个新的对象,然后将原来的对象信息一个一个的set到新的对象中。还有就是使用clone方法。使用clone方法必须满足:
- 对象的类实现Cloneable接口;
- 覆盖Object类的clone()方法 (覆盖clone()方法,访问修饰符设为public,默认是protected);
- 在clone()方法中调用super.clone();
在不使用克隆方法时,将类A的实例A1直接赋给类A的新实例A2时会出现这样的情况,修改A2的属性,A1的属性也发生了改变。这是因为赋值操作之后,A1和A2指向同一个对象,就像是使用不同的显示器操作同一个服务器一样,两个显示器显示的都是一个服务器上的内容。
三、两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
- 浅克隆:是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
- 深克隆:不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚。
1、浅克隆
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("CH" , "SD" , "QD");
Customer customer1 = new Customer(1 , 23 , address);
Customer customer2 = customer1.clone();
customer2.getAddress().setCity("JN");
customer2.setID(2);
System.out.println("customer1:"+customer1.toString());
System.out.println("customer2:"+customer2.toString());
}
}
@Data
class Customer implements Cloneable{
public int ID;
public int age;
public Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Customer(int iD, int age, Address address) {
super();
ID = iD;
this.age = age;
this.address = address;
}
@Override
public String toString() {
return "Customer [ID=" + ID + ", age=" + age + ", address=" + address
+ "]";
}
@Override
public Customer clone() throws CloneNotSupportedException {
return (Customer) super.clone();
}
}
@Data
class Address{
private String country;
private String province;
private String city;
@Override
public String toString() {
return "Address [country=" + country + ", province=" + province
+ ", city=" + city + "]";
}
public Address(String country, String province, String city) {
super();
this.country = country;
this.province = province;
this.city = city;
}
}
//输出的结果是:
//customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=JN]]
//customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
上面分析得到,clone后新旧对象互不影响,customer2修改了id后没有影响到customer1,但是修改了customer2的address属性的city值为JN后,发现customer1的address值也发生了改变。这样就没有达到完全复制、相互之间完全没有影响的目的。这样就需要进行深克隆。
2、深克隆
深克隆与浅克隆的区别就是,浅克隆不会克隆原对象中的引用类型,仅仅拷贝了引用类型的指向。深克隆则拷贝了所有。也就是说深克隆能够做到原对象和新对象之间完全没有影响。
而深克隆的实现就是在引用类型所在的类实现Cloneable接口,并使用public访问修饰符重写clone方法。
上面的代码做以下修改:
1.Address类实现Cloneable接口,重写clone方法;
@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
2.在Customer类的clone方法中调用Address类的clone方法。
@Override
public Customer clone() throws CloneNotSupportedException {
Customer customer = (Customer) super.clone();
customer.address = address.clone();
return customer;
}
修改后测试代码的输出结果:
customer1:Customer[ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
customer2:Customer[ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
发现customer2无论如何修改,customer1都没有受到影响。
实现深克隆的另一种方法就是使用序列化,将对象写入到流中,这样对象的内容就变成了字节流,也就不存在什么引用了。然后读取字节流反序列化为对象就完成了完全的复制操作了。
Address address = new Address("CH" , "SD" , "QD");
Customer customer1 = new Customer(1 , 23 , address);
Customer customer2 = (Customer) cloneObject(customer1);
customer2.getAddress().setCity("JN");
customer2.setID(2);
System.out.println("customer1:"+customer1.toString());
System.out.println("customer2:"+customer2.toString());
//customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
//customer2:Customer [ID=2, age=23, address=Address [country=CH, province=SD, city=JN]]
cloneObject方法的定义:
public static Object cloneObject(Object obj) throws IOException, ClassNotFoundException{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in =new ObjectInputStream(byteIn);
return in.readObject();
}
个人的理解是Java中定义的clone没有深浅之分,都是统一的调用Object的clone方法。为什么会有深克隆的概念?是由于我们在实现的过程中刻意的嵌套了clone方法的调用。也就是说深克隆就是在需要克隆的对象类型的类中全部实现克隆方法。就像是toString方法一样,假如上面的Customer类中重写了toString方法,而Address类没有进行重写,就会出现这样的输出语句:
- customer1:Customer[ID=1, age=23, address=com.gos.java.standard.Address@38d8fb2b]
只有在Address类也重写了toString方法才会打印出完全的信息:
- customer1:Customer [ID=1, age=23, address=Address [country=CH, province=SD, city=QD]]
只不过在打印的操作中就默认的调用了对象的toString方法,而clone方法需要在代码中显式的调用。
总结:
- 1.浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。
- 2.深克隆:是在引用类型的类中也实现了cloneable,是clone的嵌套,复制后的对象与原对象之间完全不会影响。
- 3.使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。
- 4.使用clone实现的深克隆其实是浅克隆中嵌套了浅克隆,与toString方法类似