在Java中对象的克隆有深克隆和浅克隆之分。有这种区分的原因是Java中分为基本数据类型和引用数据类型,对于不同的数据类型在内存中的存储的区域是不同的。基本数据类型存储在栈中,引用数据类型存储在堆中。
什么是克隆
克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝。
实现克隆有多种方式,可以手工的new出一个新的对象,然后将原来的对象信息一个一个的set到新的对象中。还有就是使用clone方法。使用clone方法必须满足:
-
实现Cloneable接口
-
使用public访问修饰符重新定义clone方法。
在不使用克隆方法时,将类A的实例A1直接赋给类A的新实例A2时会出现这样的情况,修改A2的属性,A1的属性也发生了改变。这是因为赋值操作之后,A1和A2指向同一个对象,就像是使用不同的显示器操作同一个服务器一样,两个显示器显示的都是一个服务器上的内容。
对于一个只含有基本数据类型的类来说使用clone方法,是完全没有问题的,用图表来表示该情形的clone操作:
Customer customer2=customer1.clone(); | ||
customer1 | ID | 123 |
age | 23 | |
customer2 | ID | 123 |
age | 23 | |
customer2.setAge(32); | ||
customer1 | ID | 123 |
age | 23 | |
customer2 | ID | 123 |
age | 32 |
在clone后customer1和customer2之间数据互不影响。
但是如果在Customer类中有一个引用类型的属性Address呢?
- 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());
- }
- }
- class Customer implements Cloneable{
- public int ID;
- public int age;
- public Address address;
- public int getID() {
- return ID;
- }
- public void setID(int iD) {
- ID = iD;
- }
- 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 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();
- }
- }
- class Address{
- private String country;
- private String province;
- private String city;
- public String getCountry() {
- return country;
- }
- public void setCountry(String country) {
- this.country = country;
- }
- 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 [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.深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,复制后的对象与原对象之间完全不会影响。
3.使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。
4.使用clone实现的深克隆其实是浅克隆中嵌套了浅克隆,与toString方法类似