Java中深拷贝和浅拷贝的研究
以这个问题开篇吧:String类型的拷贝到底是浅拷贝还是深拷贝???
准备说写一个程序来测试下,发现String没有实现Cloneable接口,也没有重写clone方法
String str1="wuanghao";
//String str2=str1.clone();//报错
String str3=str1;//复制引用
String不是基本数据类型,String也没有实现Cloneable接口,也就是说只能复制引用。
那么在修改克隆之后的对象str3之后,会不会将原来的值str1也改变了?答案是不会改变
public class StringCopy {
public static void main(String [] args){
String str1="wuanghao";
//String str2=str1.clone();
String str2=str1;
str2=str2+"wo";
System.out.println(str1);
}
}
用以上代码测试,对象str1是没有被改变的。
原因或许大家都知道
因为String是在内存中不可以被改变的对象,就比如说在for大量循环中不推荐使用+的方式来拼凑字符串一样,每次使用+都会新分配一块内存,不在原来上修改,原来的没有指向它的引用,会被回收。所以克隆相当于1个String内存空间有两个引用,当修改其中的一个值的时候,会新分配一块内存用来保存新的值,这个引用指向新的内存空间,原来的String因为还存在指向他的引用,所以不会被回收,这样,虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值。
就是因为:虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值
所以在很多情况下,我们可以把String在clone的时候和基本类型做相同的处理,只是在equal时注意一些就行了。
这样关于上面问题的答案就是:String类型是浅拷贝,但是一般把它当深拷贝对待。
1.浅拷贝与深拷贝概念
(1)浅拷贝(浅克隆)
浅拷贝又叫浅复制,将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段(java中8中原始类型)的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的还是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。
浅拷贝简单归纳就是只复制一个对象,对象内部存在指向其他对象,数组或引用则不复制。
(2)深拷贝(深克隆)
将对象中的所有字段复制到新的对象中。不过,无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。
深拷贝简单归纳就是对象内部引用的对象均复制。
2.Java中的clone()方法
(1)clone()方法将对象复制了一份并返回给调用者。一般而言,clone()方法满足下面规范:
①对任何的对象x,都有x.clone() != x;//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()== x.getClass();//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x);应该成立。
(2)Java中对象的克隆
clone()方法是在Object中定义的,而且是protected的,只有实现了Cloneable接口的类才可以在其实例上调用clone()方法,否则会抛出CloneNotSupportException。
为了获取对象的一份拷贝,我们可以利用Object类的clone()方法,也可以实现Cloneable接口,覆盖基类的clone()方法,在clone()方法中,调用super.clone()。
Cloneable接口是一个标记接口,也就是没有任何内容,定义如下:
package java.lang;
pubilc interface Cloneable{
}
例子1:对象中只含基本数据类型(完成的是深拷贝)
Student类:只含基本数据类型和Sting类型
package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
private String name;
private int age;
//将Object中的Protected换成public
@Override
public Object clone() {
Student s=null;
try {
s=(Student)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//s.professor=(Professor) professor.clone();//报错
return s;
}
// 。。。。这里还有各个属性的getter和setter方法
}
测试类
public class TestStudent {
public static void main(String[] args) {
Student s1=new Student("wuranghao", 20);
Student s2=(Student)s1.clone();
//改变s2之前s1的各个属性的值如下:
System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName());
//修改对象s2,观察对象s1是否改变
s2.setAge(18);
s2.setName("好哥");
System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+);
}
}
观察运行结果可以看到:修改对象s2后,对象s1的值是没有改变的。
即这种情况完成的是深拷贝。
例子:对象中含有引用类型(完成的是浅拷贝)
还是以上面的学生类为例,只是还添加了一个Professor类,学生类中有一个Professor的引用。
代码如下:
Professor类
package com.wrh.copy;
public class Professor {
private String name;
private int age;
public Professor(String name, int age) {
super();
this.name = name;
this.age = age;
}
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;
}
}
Student类:含有引用类型Professor
package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
private String name;
private int age;
private Professor professor;
//将Object中的Protected换成public
@Override
public Object clone() {
Student s=null;
try {
s=(Student)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//s.professor=(Professor) professor.clone();//报错
return s;
}
public Student(String name, int age, Professor professor) {
super();
this.name = name;
this.age = age;
this.professor = professor;
}
// 。。。。这里还有各个属性的getter和setter方法
}
package com.wrh.copy;
public class TestStudent {
public static void main(String[] args) {
Professor professor=new Professor("professorA",30);
Student s1=new Student("wuranghao", 20,professor);
Student s2=(Student)s1.clone();
//改变s2之前s1的各个属性的值如下:
System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
//修改对象s2,观察对象s1是否改变
s2.setAge(18);
s2.setName("好哥");
s2.getProfessor().setName("professorB");
s2.getProfessor().setAge(50);
System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
}
}
运行结果
改变s2之前s1的各个属性的值如下:20 wuranghao 30 professorA
改变s2之后s1的各个属性的值如下:20 wuranghao 50 professorB
同结果可以看出,Student 对象的拷贝,其对象中的基本数据类型和String类型发生了深拷贝,而对象中的引用类型发生了浅拷贝。
如何做到含有引用类型的对象完成深拷贝呢
其实很简单
方法:将引用类型的类也实现Cloneable接口,并将Object中clone()方法的权限Protected换成public即可。然后在需要克隆的类中的clone方法中加上一行克隆这个引用的代码即可。例如
//其中professor为s对象的一个引用
s.professor=(Professor)this.professor.clone();
具体如下:
Professor类:实现了Cloneable接口,也扩大了clone方法的权限。
package com.wrh.copy;
public class Professor implements Cloneable{
private String name;
private int age;
public Professor(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public Object clone() {
Professor p=null;
try {
p=(Professor)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return p;
}
// 。。。。这里还有各个属性的getter和setter方法
}
Student类:含有Professor引用。在clone方法中加上了 s.professor=(Professor)this.professor.clone();
代码,即使得引用类型完成深拷贝。如果不加上这行代码,则还是完成的是浅拷贝(废话,是吧)。
package com.wrh.copy;
//将一个类实现Cloneable接口,并将Object中clone()方法的权限Protected换成public
//这个类的对象就可以被拷贝
public class Student implements Cloneable{
private String name;
private int age;
private Professor professor;
//将Object中的Protected换成public
@Override
public Object clone() {
Student s=null;
try {
s=(Student)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s.professor=(Professor)this.professor.clone();//Professor实现了Clonable并重写了clone方法,这样就实现了深拷贝
return s;
}
public Student(String name, int age, Professor professor) {
super();
this.name = name;
this.age = age;
this.professor = professor;
}
// 。。。。这里还有各个属性的getter和setter方法
}
测试类:与上面的测试类一模一样。
package com.wrh.copy;
public class TestStudent {
public static void main(String[] args) {
Professor professor=new Professor("professorA",30);
Student s1=new Student("wuranghao", 20,professor);
Student s2=(Student)s1.clone();
//改变s2之前s1的各个属性的值如下:
System.out.println("改变s2之前s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
//修改对象s2,观察对象s1是否改变
s2.setAge(18);
s2.setName("好哥");
s2.getProfessor().setName("professorB");
s2.getProfessor().setAge(50);
System.out.println("改变s2之后s1的各个属性的值如下:"+s1.getAge()+" "+s1.getName()+" "+s1.getProfessor().getAge()+" "+s1.getProfessor().getName());
}
}
运行结果如下:
改变s2之前s1的各个属性的值如下:20 wuranghao 30 professorA
改变s2之后s1的各个属性的值如下:20 wuranghao 30 professorA
即对象的引用类型也完成的是深拷贝。