Java 深拷贝与浅拷贝的分析
在正式的进入主题之前,我们先来了解下深拷贝和前拷贝的概念:
浅拷贝:
会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的是基本类型的值;如果属性是内存地址,拷贝的就是内存地址,因此如果一个对象改变了这个地址就会影响到另一个对象;
深拷贝:
不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值;
了解完概念之后,我们来测试下普通的对象赋值操作属于深拷贝还是浅拷贝:
测试代码:
public class depthcopy { public static void main(string[] args) { copy first = new copy("hzw", 24); copy second = first; second.name = "shanxi"; system.out.println(first.name);//输出shanxi } } class copy { public string name; public int age; public copy(string name,int age) { this.name = name; this.age = age; } }
可以发现,在second将name属性值修改为shanxi之后,first的name属性值也变成了shanxi,这点就可以看出普通的对象赋值属于浅拷贝;
明白了对象之间赋值是浅拷贝之后,接下来我们来看看克隆到底是深拷贝还是浅拷贝,测试代码是让上面的copy对象实现cloneable接口里面的clone方法:
public class depthcopy { public static void main(string[] args) { copy first = new copy("hzw", 24); copy second = null; try { second = (copy) first.clone(); } catch (clonenotsupportedexception e) { e.printstacktrace(); } second.name = "shanxi"; system.out.println(first.name);//输出: hzw system.out.println(first);//输出: com.hzw.day33.copy@7f39ebdb system.out.println(second);//输出: com.hzw.day33.copy@33abb81e } } class copy implements cloneable { public string name; public int age; public copy(string name,int age) { this.name = name; this.age = age; } @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
可以看出原先创建出的对象first和克隆创建出的对象second是两个实例,因此对于second中name属性的修改并不会影响first中的name属性;但是,我们并不能单纯的认为克隆就是深拷贝的,比如下面这个例子:
public class depthcopy { public static void main(string[] args) { student student = new student(95); copy first = new copy("hzw", 24,student); copy second = null; try { second = (copy) first.clone(); } catch (clonenotsupportedexception e) { e.printstacktrace(); } second.name = "shanxi"; second.student.score = 60; system.out.println(first == second);//false system.out.println(first.student == second.student);//true system.out.println(first.student.score);//60 } } class copy implements cloneable { public string name; public int age; public student student; public copy(string name,int age,student student) { this.name = name; this.age = age; this.student = student; } @override protected object clone() throws clonenotsupportedexception { return super.clone(); } } class student { public int score; public student(int score) { this.score = score; } }
看到没有呢?我们通过克隆的方式创建了second,很明显发现first和second是两个实例,因为first == second输出为false,但是first和second里面的student对象却是一样的,通过second修改了student的score值之后,first里面student的score也发生了改变,这也就是说first和second里面的student是相同的,这也就说明了克隆是浅拷贝的,我们要想实现克隆的深拷贝,必须让copy对象里面的student对象也要实现cloneable接口里面的clone方法,并且在copy里面的克隆方法返回student的一个克隆即可,这样就可以保证student的唯一啦,修改之后的代码如下:
public class depthcopy { public static void main(string[] args) { student student = new student(95); copy first = new copy("hzw", 24,student); copy second = null; try { second = (copy) first.clone(); } catch (clonenotsupportedexception e) { e.printstacktrace(); } second.name = "shanxi"; second.student.score = 60; system.out.println(first == second);//false system.out.println(first.student == second.student);//false system.out.println(first.student.score);//95 system.out.println(second.student.score);//60 } } class copy implements cloneable { public string name; public int age; public student student; public copy(string name,int age,student student) { this.name = name; this.age = age; this.student = student; } @override protected object clone() throws clonenotsupportedexception { copy copy = (copy)super.clone(); copy.student = (student) student.clone(); return copy; } } class student implements cloneable { public int score; public student(int score) { this.score = score; } @override protected object clone() throws clonenotsupportedexception { return super.clone(); } }
可以看到此时first和second和first.student和second.student都不是相同的,因此我们修改second的student的score之后并没有影响到first里的student的score值,达到了深拷贝的目的;
但是,仔细一想问题就出来了,假如我们上面例子的student类中也存在引用类型的属性,比如college类,那么我们必须让college类实现cloneable接口,然后在student类里面的clone方法里面调用college类的clone方法,在copy类的clone方法中调用student类的clone方法,发现没有了,这个过程好复杂,必须让类中的有关引用类型全部实现cloneable接口,感觉好麻烦是不是,好的,接下来就该牛人登场了;
解决深拷贝问题最好的方式就是采用序列化方式,这样各种类均不用实现cloneable接口的,直接序列化反序列化就可以啦,我们来见识下吧。
import java.io.file; import java.io.fileinputstream; import java.io.fileoutputstream; import java.io.objectinputstream; import java.io.objectoutputstream; import java.io.serializable; public class depthcopy { public static void main(string[] args) { college school = new college("nongda"); student student = new student(95, school); copy copy = new copy("hzw",23, student); copy another = null;//表示反序列化出来的类实例 //进行序列化操作 try { fileoutputstream fos = new fileoutputstream(new file("d:/copy.txt")); objectoutputstream oos = new objectoutputstream(fos); oos.writeobject(copy); } catch (exception e) { e.printstacktrace(); } //进行反序列化操作 fileinputstream fis; try { fis = new fileinputstream(new file("d:/copy.txt")); objectinputstream ois = new objectinputstream(fis); another = (copy) ois.readobject(); } catch (exception e) { e.printstacktrace(); } system.out.println(copy == another);//false system.out.println(copy.student == another.student);//false system.out.println(copy.student.school == another.student.school);//false another.student.school.schoolname = "wuda"; system.out.println(copy.student.school.schoolname);//nongda } } class copy implements serializable { public string name; public int age; public student student; public copy(string name,int age,student student) { this.name = name; this.age = age; this.student = student; } } class student implements serializable { public int score; public college school; public student(int score,college school) { this.score = score; this.school = school; } } class college implements serializable { public string schoolname; public college(string schoolname) { this.schoolname = schoolname; } }
从输出就可以看出来,反序列化之后生成的对象完全就是对原对象的一份拷贝,除了属性值相同之外并不和原对象有任何关系,因此当我们修改反序列化生成对象的schoolname为"wuda"的时候并没有修改原来实例的schoolname值,还是输出"nongda",因此达到了真正的深拷贝效果,但是要想实现序列化,所有的有关类都必须实现serializable接口,这总也比既实现cloneable接口又实现clone方法更方便吧。
以上就是对java 深拷贝和浅拷贝的详细讲解,有需要的可以参考下。
上一篇: java 遍历MAP的几种方法示例代码
下一篇: PHP中的正则表达式实例详解