前言
在解说什么是深拷贝和浅拷贝之前,咱们先来理解一下什么是根本类型和援用类型。
根本类型和援用类型
根本类型也称为值类型,别离是字符类型 char,布尔类型 boolean以及数值类型 byte、short、int、long、float、double。
援用类型则包含类、接口、数组、枚举等。
Java 将内存空间分为堆和栈。根本类型间接在栈中存储数值,而援用类型是将援用放在栈中,理论存储的值是放在堆中,通过栈中的援用指向堆中寄存的数据。
上图定义的 a 和 b 都是根本类型,其值是间接寄存在栈中的;而 c 和 d 是 String 申明的,这是一个援用类型,援用地址是寄存在 栈中,而后指向堆的内存空间。
上面 d = c;这条语句示意将 c 的援用赋值给 d,那么 c 和 d 将指向同一块堆内存空间。
Clone办法
本篇博客咱们解说的是 Java 的深拷贝和浅拷贝,其实现形式正是通过调用 Object 类的 clone() 办法来实现。在 Object.class 类中,源码为:
protected native Object clone() throws CloneNotSupportedException;
这是一个用 native 关键字润饰的办法,只须要晓得用 native 润饰的办法就是通知操作系统,这个办法我不实现了,让操作系统去实现。具体怎么实现咱们不须要理解,只须要晓得 clone办法的作用就是复制对象,产生一个新的对象。
浅拷贝
浅拷贝是按位拷贝对象,它会创立一个新对象,这个对象有着原始对象属性值的一份准确拷贝。如果属性是根本类型,拷贝的就是根本类型的值;如果属性是内存地址(援用类型),拷贝的就是内存地址 ,因而如果其中一个对象扭转了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(一一成员顺次拷贝),即只复制对象空间而不复制资源。
实现对象拷贝的类,须要实现 Cloneable
接口,并覆写 clone()
办法。
public class Address { private String province; private String city; public void setAddress(String province, String city) { this.province = province; this.city = city; } @Override public String toString() { return "Address [province=" + province + ", city=" + city + "]"; }}
public class Student implements Cloneable { private String name; private int age; private Address address; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; this.address = new Address(); } 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; } public void setAddress(String province, String city) { address.setAddress(province, city); } public void display(String name) { System.out.println(name + ":" + "name=" + name + ", age=" + age + "," + address); } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); }}
这是一个咱们要进行赋值的原始类 Student。上面咱们产生一个 Student对象,并调用其 clone 办法复制一个新的对象。
留神:
调用对象的 clone 办法,必须要让类实现 Cloneable 接口,并且覆写 clone 办法。
测试:
public static void main(String[] args) throws CloneNotSupportedException { Student s1 = new Student("小明",20); s1.setAddress("安徽","合肥"); Student s2 = (Student) s1.clone(); System.out.println("S1:"+s1); System.out.println("s1.getName:"+s1.getName().hashCode()); System.out.println("S2:"+s2); System.out.println("s2.getName:"+s2.getName().hashCode()); s1.display("s1"); s2.display("s2"); s2.setAddress("安徽","安庆"); s1.display("s1"); s2.display("s2"); }
输入后果:
S1:org.example.jvm.Student@2a84aee7s1.getName:756703S2:org.example.jvm.Student@a09ee92s2.getName:756703s1:name=s1, age=20,Address [province=安徽, city=合肥]s2:name=s2, age=20,Address [province=安徽, city=合肥]s1:name=s1, age=20,Address [province=安徽, city=安庆]s2:name=s2, age=20,Address [province=安徽, city=安庆]
首先咱们创立一个Student类的对象 s1,其name 为小明,age为20,地址类 Address 两个属性为 安徽和合肥。接着咱们调用 clone() 办法复制另一个对象 s2,接着打印这两个对象的内容。
剖析后果:
- 从第 1 行和第 3 行打印后果来看,这是两个不同的对象。
- 从第 5 行和第 6 行打印的对象内容看,原对象 s1 和克隆进去的对象 s2 内容完全相同。
- 咱们更改一下克隆对象 s2 的属性 Address 为
安徽安庆
(原对象 s1 是安徽合肥
),然而从第 7 行和第 8 行打印后果来看,原对象 s1 和克隆对象 s2 的 Address 属性都被批改了。 - 对象 Student 的属性 Address,通过 clone 之后,其实只是复制了其援用,他们指向的还是同一块堆内存空间,当批改其中一个对象的属性 Address,另一个也会跟着变动。
浅拷贝:创立一个新对象,而后将以后对象的非动态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是援用类型的话,则复制援用但不复制援用的对象。因而,原始对象及其正本援用同一个对象。
深拷贝
深拷贝,在拷贝援用类型成员变量时,为援用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
对于 Student
的援用类型的成员变量 Address
,须要实现 Cloneable
并重写 clone()
办法。
public class Address implements Cloneable{ private String province; private String city; public void setAddress(String province, String city) { this.province = province; this.city = city; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Address [province=" + province + ", city=" + city + "]"; }}
在 Student
的 clone()
办法中,须要拿到拷贝本人后产生的新的对象,而后对新的对象的援用类型再调用拷贝操作,实现对援用类型成员变量的深拷贝。
public class Student implements Cloneable { private String name; private int age; private Address address; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; this.address = new Address(); } 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; } public void setAddress(String province, String city) { address.setAddress(province, city); } public void display(String name) { System.out.println(name + ":" + "name=" + name + ", age=" + age + "," + address); } @Override protected Object clone() throws CloneNotSupportedException { Student s = (Student) super.clone(); s.address = (Address) address.clone(); return s; }}
测试:
public static void main(String[] args) throws CloneNotSupportedException { Student s1 = new Student("小明",20); s1.setAddress("安徽","合肥"); Student s2 = (Student) s1.clone(); System.out.println("S1:"+s1); System.out.println("s1.getName:"+s1.getName().hashCode()); System.out.println("S2:"+s2); System.out.println("s2.getName:"+s2.getName().hashCode()); s1.display("s1"); s2.display("s2"); s2.setAddress("安徽","安庆"); s1.display("s1"); s2.display("s2"); }
输入后果:
S1:org.example.jvm.Student@2a84aee7s1.getName:756703S2:org.example.jvm.Student@a09ee92s2.getName:756703s1:name=s1, age=20,Address [province=安徽, city=合肥]s2:name=s2, age=20,Address [province=安徽, city=合肥]s1:name=s1, age=20,Address [province=安徽, city=合肥]s2:name=s2, age=20,Address [province=安徽, city=安庆]
由输入后果可知,深拷贝后,不论是根底数据类型还是援用类型的成员变量,批改其值都不会互相造成影响。
留神:
然而这种做法有个弊病,这里咱们Student
类只有一个 Address
援用类型,而 Address
类没有,所以咱们只用重写 Address
类的clone
办法,然而如果 Address
类也存在一个援用类型,那么咱们也要重写其clone
办法,这样上来,有多少个援用类型,咱们就要重写多少次,如果存在很多援用类型,那么代码量显然会很大,所以这种办法不太适合。
还有一种形式能够实现深拷贝:利用序列化
序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取进去。
这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以咱们能够利用对象的序列化产生克隆对象,而后通过反序列化获取这个对象。
留神每个须要序列化的类都要实现 Serializable 接口,如果有某个属性不须要序列化,能够将其申明为 transient,行将其排除在克隆属性之外。
public class Address implements Serializable { private String province; private String city; public void setAddress(String province, String city) { this.province = province; this.city = city; } @Override public String toString() { return "Address [province=" + province + ", city=" + city + "]"; }}
public class Student implements Serializable { private String name; private int age; private Address address; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; this.address = new Address(); } 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; } public void setAddress(String province, String city) { address.setAddress(province, city); } public void display(String name) { System.out.println(name + ":" + "name=" + name + ", age=" + age + "," + address); } //深度拷贝 public Object deepClone() throws Exception{ // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); }}
测试:
public static void main(String[] args) throws Exception { Student s1 = new Student("小明",20); s1.setAddress("安徽","合肥"); Student s2 = (Student) s1.deepClone(); System.out.println("S1:"+s1); System.out.println("s1.getName:"+s1.getName().hashCode()); System.out.println("S2:"+s2); System.out.println("s2.getName:"+s2.getName().hashCode()); s1.display("s1"); s2.display("s2"); s2.setAddress("安徽","安庆"); s1.display("s1"); s2.display("s2"); }
输入后果:
S1:org.example.jvm.Student@3f99bd52s1.getName:756703S2:org.example.jvm.Student@1f17ae12s2.getName:756703s1:name=s1, age=20,Address [province=安徽, city=合肥]s2:name=s2, age=20,Address [province=安徽, city=合肥]s1:name=s1, age=20,Address [province=安徽, city=合肥]s2:name=s2, age=20,Address [province=安徽, city=安庆]
因为序列化产生的是两个齐全独立的对象,所有无论嵌套多少个援用类型,序列化都是能实现深拷贝的。
总结
至此,置信什么是深拷贝,什么是浅拷贝,置信你肯定明确了。