直接赋值、浅拷贝和深拷贝
直接赋值
平时最常用的方式,在代码中的体现是Persona = new Person();Person b = a,是一种简单明了的方式,但是它只是拷贝了对象引用地址而已,并没有在内存中生成新的对象,只是一个地址两个别名罢了,改变一个,另一个都会改变。
![](https://img.haomeiwen.com/i23281927/0dbe199a2ce752d8.png)
public class DirectAssign {
public static void main(String[] args) {
DirectTeacher t1 = new DirectTeacher("Chinese", 3, 5);
DirectTeacher t2 = t1;
System.out.println("t1 : " + t1 + " , t2 : " + t2);
t2.setFloor(4);
t2.setLevel(2);
t2.setSubject("Math");
System.out.println("t1 : " + t1 + " , t2 : " + t2);
}
/*
* t1 : DirectTeacher{subject='Chinese', level=3, floor=5} , t2 : DirectTeacher{subject='Chinese', level=3, floor=5}
* t1 : DirectTeacher{subject='Math', level=2, floor=4} , t2 : DirectTeacher{subject='Math', level=2, floor=4}
*/
}
class DirectTeacher {
private String subject;
private Integer level;
private int floor;
public DirectTeacher(String subject, Integer level, int floor) {
this.subject = subject;
this.level = level;
this.floor = floor;
}
getter、setter略
浅拷贝
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
![](https://img.haomeiwen.com/i23281927/8385bccd3fd444d7.png)
实现浅拷贝,只需要实现cloneable接口并且重写clone方法,clone方法是属于object类的,况且它是protected修饰的,不能直接调用的,所以需要重写,clone还需要靠实现cloneable接口调用底层的,还有一点就是,验证深浅拷贝,需要一个可变对象,用不可变对象是验证不出来的。看下面例子,注意,数组对象是可变对象来的。
public class ShallowDeepCopy {
/**
* 之所以定义那么多class,因为需要重写clone方法,原本的Object对象的clone方法是protected的,不能直接调用的。
*/
public static void main(String[] args) throws CloneNotSupportedException {
ShallowStudent ss1 = new ShallowStudent("lily", 12, 155, new int[]{50, 60, 70});
//这是浅拷贝
ShallowStudent ss2 = (ShallowStudent) ss1.clone();
System.out.println("ss1: " + ss1 + ",detail: " + ss1.showShallowStudentDetail() + ",ss1 hashcode: " + ss1.hashCode());
System.out.println("ss2: " + ss2 + ",detail: " + ss2.showShallowStudentDetail() + ",ss2 hashcode: " + ss2.hashCode());
System.out.println("ss1.height == ss2.height? " + (ss1.getHeight() == ss2.getHeight()));
System.out.println("ss1.age == ss2.age? " + (ss1.getAge() == ss2.getAge()));
System.out.println("ss1.name == ss2.name? " + (ss1.getName() == ss2.getName()));
System.out.println("ss1.cmeScores == ss2.cmeScores? " + (ss1.getCmeScores() == ss2.getCmeScores()));
System.out.println("change ss1 age = 15 , name = orion , height = 162 , cmeScores[0] = 22:");
ss1.setAge(15);
ss1.setName("orion");
ss1.setHeight(162);
ss1.getCmeScores()[0] = 22;
System.out.println("ss1: " + ss1 + ",detail: " + ss1.showShallowStudentDetail() + ",ss1 hashcode: " + ss1.hashCode());
System.out.println("ss2: " + ss2 + ",detail: " + ss2.showShallowStudentDetail() + ",ss2 hashcode: " + ss2.hashCode());
/**
*
* 结果如下:
ss1: com.orion.ShallowStudent@28d93b30,detail: ShallowStudent{name='lily', cmeScores=[50, 60, 70], age=12, height=155},ss1 hashcode: 685325104
ss2: com.orion.ShallowStudent@1b6d3586,detail: ShallowStudent{name='lily', cmeScores=[50, 60, 70], age=12, height=155},ss2 hashcode: 460141958
ss1.height == ss2.height? true
ss1.age == ss2.age? true
ss1.name == ss2.name? true
ss1.cmeScores == ss2.cmeScores? true
change ss1 age = 15 , name = orion , height = 162 , cmeScores[0] = 22:
ss1: com.orion.ShallowStudent@28d93b30,detail: ShallowStudent{name='orion', cmeScores=[22, 60, 70], age=15, height=162},ss1 hashcode: 685325104
ss2: com.orion.ShallowStudent@1b6d3586,detail: ShallowStudent{name='lily', cmeScores=[22, 60, 70], age=12, height=155},ss2 hashcode: 460141958
*
* 结论:ShallowStudent实现了cloneable接口,重写了clone方法,创建1个对象,另一个clone,它们在栈中是分配两个地址的,
* 都是指向同一个堆地址,height是int基本类型,其他都是对象类型,height直接比较value没问题,而改变了ss2的name为什么ss1没有改变呢,
* 因为string是不可变类,它会再生一个string,而ss2改变了指向,所以ss1不变,ss2变,age也是integer不可变类也是同样道理,
* 还有ss1和ss2的name、age用==来比较的话,结果是true,证明它们指向的地址是一样的,
* 而数组对象cmeScores不是不可变对象,ss1和ss2指向同一个地址,改变了它,两个都是会发生变化的。
*
* 所以,我们来验证深浅拷贝,需要一个可变对象,用不可变对象是验证不出来的。
*/
}
}
/**
* 非常需要注意的是,重写clone方法,是要实现cloneable接口的,
* 如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
*/
class ShallowStudent implements Cloneable {
private String name;
private Integer age;
private int height;
private int[] cmeScores;
public ShallowStudent(String name, Integer age, int height, int[] cmeScores) {
this.name = name;
this.age = age;
this.height = height;
this.cmeScores = cmeScores;
}
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 int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int[] getCmeScores() {
return cmeScores;
}
public void setCmeScores(int[] cmeScores) {
this.cmeScores = cmeScores;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String showShallowStudentDetail() {
return "ShallowStudent{" +
"name='" + name + '\'' +
", cmeScores=" + Arrays.toString(cmeScores) +
", age=" + age +
", height=" + height +
'}';
}
}
深拷贝:
深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象,简单点说就是拷贝对象和被拷贝对象没有任何关系,互不影响
![](https://img.haomeiwen.com/i23281927/3d117b9ea35ccd1d.png)
这里需要注意一下:为什么要弄别的对象做属性,比如Father、Son类,因为上面浅拷贝的例子,它的属性都是不可变对象,就算改变了它们,它们底层是生成了一个新的对象并应用它。现在用Father、Son类做参数,目的就是为了使用可变对象做属性来测试深浅拷贝。
注意下面,grandfather、son实现cloneable接口并重写clone方法,father是没有的。
public classDeepCopy {
/**
* 之所以定义那么多class,因为需要重写clone方法,原本的Object对象的clone方法是protected的,不能直接调用的。
*/
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("=====================不使用数组,使用2个自定义对象,可变对象参数不实现cloneable接口=====================");
GrandFather gf1 = new GrandFather(70, "grandFather", new Father(40, "father"));
GrandFather gf2 = (GrandFather) gf1.clone();
System.out.println("gf1: " + gf1 + ",detail: " + gf1.showGrandFatherDetail() + ",gf1 hashcode: " + gf1.hashCode());
System.out.println("gf2: " + gf2 + ",detail: " + gf2.showGrandFatherDetail() + ",gf2 hashcode: " + gf2.hashCode());
System.out.println("clone gf2 change gfName = fakeGf , gfAge = 80 , father set fName = fakeF and fAge = 50");
gf2.setGfName("fakeGf");
gf2.setGfAge(80);
gf2.getFather().setfAge(50);
gf2.getFather().setfName("fakeF");
System.out.println("gf1: " + gf1 + ",detail: " + gf1.showGrandFatherDetail() + ",gf1 hashcode: " + gf1.hashCode());
System.out.println("gf2: " + gf2 + ",detail: " + gf2.showGrandFatherDetail() + ",gf2 hashcode: " + gf2.hashCode());
/**
* 运行结果:
gf1: com.orion.GrandFather@4554617c,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=40, fName='father'}},gf1 hashcode: 1163157884
gf2: com.orion.GrandFather@74a14482,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=40, fName='father'}},gf2 hashcode: 1956725890
clone gf2 change gfName = fakeGf , gfAge = 80 , father set fName = fakeF and fAge = 50
gf1: com.orion.GrandFather@4554617c,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=50, fName='fakeF'}},gf1 hashcode: 1163157884
gf2: com.orion.GrandFather@74a14482,detail: GrandFather{gfAge=80, gfName='fakeGf', father=Father{fAge=50, fName='fakeF'}},gf2 hashcode: 1956725890
*
* 结论:
* GrandFather有实现cloneable接口并且重写clone方法,而它的属性Father对象是没有实现cloneable接口的,
* 克隆体gf2改变了father属性的子属性时,gf1也被改变了,结果可证,这也是一个浅拷贝。
*
*/
System.out.println("=====================不使用数组,使用2个自定义对象,可变对象参数实现cloneable接口=====================");
GrandFather gf3 = new GrandFather(99, "grandFather", new Father(44, "father"), new Son(22, "son"));
GrandFather gf4 = (GrandFather) gf3.clone();
System.out.println("gf3: " + gf3 + ",detail: " + gf3.showGrandFatherDetail() + ",gf3 hashcode: " + gf3.hashCode());
System.out.println("gf4: " + gf4 + ",detail: " + gf4.showGrandFatherDetail() + ",gf4 hashcode: " + gf4.hashCode());
System.out.println("clone gf4 change gfName = trueGF , gfAge = 88 , father set fName = trueF and fAge = 55 , son set sName= trueS and sAge = 33");
gf4.setGfName("trueGF");
gf4.setGfAge(88);
gf4.getFather().setfAge(55);
gf4.getFather().setfName("trueF");
gf4.getSon().setsAge(33);
gf4.getSon().setsName("trueS");
System.out.println("gf3: " + gf3 + ",detail: " + gf3.showGrandFatherDetail() + ",gf3 hashcode: " + gf3.hashCode());
System.out.println("gf4: " + gf4 + ",detail: " + gf4.showGrandFatherDetail() + ",gf4 hashcode: " + gf4.hashCode());
/**
* 运行结果:
gf3: com.orion.GrandFather@1540e19d,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=44, fName='father'}, son=Son{sAge=22, sName='son'}},gf3 hashcode: 356573597
gf4: com.orion.GrandFather@677327b6,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=44, fName='father'}, son=Son{sAge=22, sName='son'}},gf4 hashcode: 1735600054
clone gf4 change gfName = trueGF , gfAge = 88 , father set fName = trueF and fAge = 55 , son set sName= trueS and sAge = 33
gf3: com.orion.GrandFather@1540e19d,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=55, fName='trueF'}, son=Son{sAge=22, sName='son'}},gf3 hashcode: 356573597
gf4: com.orion.GrandFather@677327b6,detail: GrandFather{gfAge=88, gfName='trueGF', father=Father{fAge=55, fName='trueF'}, son=Son{sAge=33, sName='trueS'}},gf4 hashcode: 1735600054
*
* 结论:
* GrandFather有实现cloneable接口并且重写clone方法,而它的属性Father对象是没有实现cloneable接口的,属性son有实现cloneable方法并重写clone方法
* 同时GrandFather的clone方法也有子属性son的clone方法,如果子属性的子属性还有,就要一层一层嵌下去,比较麻烦,所以还有一种是serialize的方法进行深拷贝。
* 由结果可以知道,实现了cloneable接口的son属性在被gf4改变的时候,gf3没有改变,说明他们的各自的son是独立的。
* 所以实现深拷贝,就都需要实现cloneable接口并且重写clone方法,父类的clone方法还需要对子属性的clone。
*/
}
}
class GrandFather implements Cloneable {
private int gfAge;
private String gfName;
private Father father;
private Son son;
public GrandFather(int gfAge, String gfName) {
this.gfAge = gfAge;
this.gfName = gfName;
}
public GrandFather(int gfAge, String gfName, Father father) {
this.gfAge = gfAge;
this.gfName = gfName;
this.father = father;
}
public GrandFather(int gfAge, String gfName, Son son) {
this.gfAge = gfAge;
this.gfName = gfName;
this.son = son;
}
public GrandFather(int gfAge, String gfName, Father father, Son son) {
this.gfAge = gfAge;
this.gfName = gfName;
this.father = father;
this.son = son;
}
public int getGfAge() {
return gfAge;
}
public void setGfAge(int gfAge) {
this.gfAge = gfAge;
}
public String getGfName() {
return gfName;
}
public void setGfName(String gfName) {
this.gfName = gfName;
}
public Father getFather() {
return father;
}
public void setFather(Father father) {
this.father = father;
}
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
public String showGrandFatherDetail() {
return "GrandFather{" +
"gfAge=" + gfAge +
", gfName='" + gfName + '\'' +
(father == null ? "" : ", father=" + father.showFatherDetail()) +
(son == null ? "" : ", son=" + son.showSonDetail()) +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
GrandFather grandFather = (GrandFather) super.clone();
//深拷贝,除了子对象属性要实现cloneable接口重写clone方法,自身也要做同样操作,而且还要在自己的clone方法中写上子属性的
//clone方法,有多少就嵌入多少,一直传递。
if (son != null) {
grandFather.son = (Son) son.clone();
}
return grandFather;
}
}
class Father {
private int fAge;
private String fName;
public Father(int fAge, String fName) {
this.fAge = fAge;
this.fName = fName;
}
public int getfAge() {
return fAge;
}
public void setfAge(int fAge) {
this.fAge = fAge;
}
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public String showFatherDetail() {
return "Father{" +
"fAge=" + fAge +
", fName='" + fName + '\'' +
'}';
}
}
class Son implements Cloneable {
private int sAge;
private String sName;
public Son(int sAge, String sName) {
this.sAge = sAge;
this.sName = sName;
}
public int getsAge() {
return sAge;
}
public void setsAge(int sAge) {
this.sAge = sAge;
}
public String getsName() {
return sName;
}
public void setsName(String sName) {
this.sName = sName;
}
public String showSonDetail() {
return "Son{" +
"sAge=" + sAge +
", sName='" + sName + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
序列化方式的深拷贝
实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题,多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,比较方便。
/**
* 序列化方式进行深拷贝,避免层层clone,各个对象都要实现序列化Serializable接口
*
* @author Administrator
*/
public class SerializableDeepCopy {
public static void main(String[] args) {
SmartPhone sp = new SmartPhone("smartPhone", 1000);
Phone phone1 = new Phone("phone", 5000, sp);
Phone phone2 = phone1.cloneit();
System.out.println("phone1: " + phone1 + ",detail: " + phone1.showPhoneDetail() + ",ss1 hashcode: " + phone1.hashCode());
System.out.println("phone2: " + phone2 + ",detail: " + phone2.showPhoneDetail() + ",ss2 hashcode: " + phone2.hashCode());
System.out.println("change phone2 name = fakePhone2 , name = 3000 , set smartPhone name = fakeSmartPhone2 , holdTime = 9000");
phone2.setName("fakePhone2");
phone2.setHoldTime(3000);
phone2.getSp().setName("fakeSmartPhone2");
phone2.getSp().setHoldTime(9000);
System.out.println("phone1: " + phone1 + ",detail: " + phone1.showPhoneDetail() + ",ss1 hashcode: " + phone1.hashCode());
System.out.println("phone2: " + phone2 + ",detail: " + phone2.showPhoneDetail() + ",ss2 hashcode: " + phone2.hashCode());
/**
* 运行结果:
phone1: com.orion.Phone@7f31245a,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss1 hashcode: 2133927002
phone2: com.orion.Phone@4f3f5b24,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss2 hashcode: 1329552164
change phone2 name = fakePhone2 , name = 3000 , set smartPhone name = fakeSmartPhone2 , holdTime = 9000
phone1: com.orion.Phone@7f31245a,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss1 hashcode: 2133927002
phone2: com.orion.Phone@4f3f5b24,detail: Phone{name='fakePhone2', holdTime=3000, sp=SmartPhone{name='fakeSmartPhone2', holdTime=9000}},ss2 hashcode: 1329552164
*
* 可见,通过serializable的深拷贝是成功的,phone2是phone1克隆出来,但phone2变化并不影响到phone1。
*/
}
}
class Phone implements Serializable {
private String name;
private int holdTime;
private SmartPhone sp;
public Phone(String name, int holdTime, SmartPhone sp) {
this.name = name;
this.holdTime = holdTime;
this.sp = sp;
}
public Phone cloneit() {
Phone phone = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Phone) ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return phone;
}
public String showPhoneDetail() {
return "Phone{" +
"name='" + name + '\'' +
", holdTime=" + holdTime +
(sp == null ? "" : ", sp=" + sp.showSmartPhoneDetail()) +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHoldTime() {
return holdTime;
}
public void setHoldTime(int holdTime) {
this.holdTime = holdTime;
}
public SmartPhone getSp() {
return sp;
}
public void setSp(SmartPhone sp) {
this.sp = sp;
}
}
class SmartPhone implements Serializable {
private String name;
private int holdTime;
public SmartPhone(String name, int holdTime) {
this.name = name;
this.holdTime = holdTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHoldTime() {
return holdTime;
}
public void setHoldTime(int holdTime) {
this.holdTime = holdTime;
}
public String showSmartPhoneDetail() {
return "SmartPhone{" +
"name='" + name + '\'' +
", holdTime=" + holdTime +
'}';
}
}
参考: