java 通过Object的clone复制对象
需求背景
对象的克隆是指创建一个新的对象,且新的对象的状态与原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态。
常规实现
运行结果
这个时候发现如果改了p2的地址属性,p1的地址属性也改变了
原因分析
出现这种情况的原因Person p2 = p1;这里将p1赋值给p2实际是将p1的引用给p2,在堆内存中p1和p2指向的是同一个对象,怎样才能实现最初的需求呢?
需求实现
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,clone()是object类的protected 方法,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
同时只有类的对象自己可以克隆自己,所以对象类必须实现Cloneable接口才可以使用obj.clone()方法,
首先,如果这个对象的类不实现接口{@code Cloneable},然后{@code CloneNotSupportedException}被抛出,典型的方式:
代码实现
运行结果
此时的结果p2更改的地址属性并没有影响到p1的地址属性,需求达到。
到此为止实现的对象clone方法属于浅复制(shallow copy),那么什么是深复制(deep copy)
深复制
大家都知道,在java语言中,分为基本数据类型和引用数据类型,基本数据类型包括(byte、short、int、long、float、double、boolean、char),引用数据类型包括(class、interface、[ ]),浅复制和深复制的主要区别就在于是否支持引用类型的成员变量的复制。
浅复制,clone()内部类似于创建一个新的对象并把对象中相应的字段通过赋值给新的对象,而引用数据类型的内容本身并不是克隆的,因此这种复制就叫浅复制。
那么
代码重现
增加Study对象
改造Person对象
运行程序
运行结果
可以看到p2更改的基础属性地址信息不影响p1的,但是p2更改的Study对象属性却会影响p1中对应的Study属性,这就是浅复制存在的问题
问题处理
在这种情况下需要对Study对象也实现Cloneable接口,同时重写clone方法覆盖
同时Person对象中需要处理Study对象的复制
这就是深复制
再次运行copy3()查看运行结果
这里可以看到p2对study属性的更改不再影响p1中study的属性。目标达成。
测试代码
TestClone.java
package com.example.demo; import com.example.modules.person.domain.Person; import com.example.modules.person.domain.Study; /** * @author: dongao * @create: 2020/6/6 */ public class TestClone { //测试clone public static void main(String[] args) { //copy(); //copy2(); copy3(); } private static void copy() { Person p1 = new Person(); p1.setName("小A"); p1.setAddr("北京"); p1.setAge(18); p1.setBirthday("2020-05-20"); p1.setCountry("中国"); System.out.println("p1-first:"+p1.getAddr()); Person p2 = p1; System.out.println("p2-first:"+p2.getAddr()); p2.setAddr("上海"); System.out.println("p1-second:"+p1.getAddr()); System.out.println("p2-second:"+p2.getAddr()); } private static void copy2() { Person p1 = new Person(); p1.setName("小A"); p1.setAddr("北京"); p1.setAge(18); p1.setBirthday("2020-05-20"); p1.setCountry("中国"); System.out.println("p1-first:"+p1.getAddr()); Person p2 = p1.clone(); System.out.println("p2-first:"+p2.getAddr()); p2.setAddr("上海"); System.out.println("p1-second:"+p1.getAddr()); System.out.println("p2-second:"+p2.getAddr()); } private static void copy3() { Person p1 = new Person(); p1.setName("小A"); p1.setAddr("北京"); p1.setAge(18); p1.setBirthday("2020-05-20"); p1.setCountry("中国"); Study s1 = new Study(); s1.setStuName("语文"); p1.setStudy(s1); System.out.println("p1-first:"+p1.getAddr()+"study:"+p1.getStudy().getStuName()); Person p2 = p1.clone(); System.out.println("p2-first:"+p2.getAddr()+"study:"+p2.getStudy().getStuName()); p2.setAddr("上海"); p2.getStudy().setStuName("数学"); System.out.println("p1-second:"+p1.getAddr()+"study:"+p1.getStudy().getStuName()); System.out.println("p2-second:"+p2.getAddr()+"study:"+p2.getStudy().getStuName()); } }
Person.java
package com.example.modules.person.domain; public class Person implements Cloneable { private String id; private String name; private Integer age; private String country; private String birthday; private String addr; private String data; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getBirthday() { return birthday; } public void setBirthday(String birthday) { this.birthday = birthday; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } public String getData() { return data; } public void setData(String data) { this.data = data; } private Study study; public Study getStudy() { return study; } public void setStudy(Study study) { this.study = study; } @Override public Person clone() { Person per = null; try { per = (Person) super.clone(); per.study = study.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return per; } }
Study.java
package com.example.modules.person.domain; /** * @author: dongao * @create: 2020/6/6 */ public class Study implements Cloneable{ private String stuName; public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } @Override public Study clone(){ Study study = null; try { study = (Study) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return study; } }
流方式复制对象
到这里基本问题都解决了,但是还可能会遇到一个问题,就是当前对象中有很多引用对象,这样的话通过clone的方法处理起来会比较麻烦,这是可以采用另外一种方式,序列化的方式来实现对象的深复制。
改造Person.java
这里最好显式指定serialVersionUID的值防止反序列化的时候出问题。
public Person pclone() { Person per = null; try { //对象的序列化流,作用:把对象转成字节数据的输出到文件中保存,对象的输出过程称为序列化,可实现对象的持久存储。 ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(this); out.close(); // read a clone of the object from the byte array ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream in = new ObjectInputStream(bin); per = (Person) in.readObject(); in.close(); } catch (Exception e) { e.printStackTrace(); } return per; }
运行效果
**注:**大家可以自行复制代码测试,欢迎指正,相互学习。