Java基础知识之深拷贝和浅拷贝
对象拷贝是什么?
Java中的对象拷贝(Object Copy)指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去。举例说明:比如,对象A和对象B都属于类S,具有属性a和b。那么对对象A进行拷贝操作赋值给对象B就是:B.a=A.a; B.b=A.b;
Java中的对象拷贝主要分为:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。
Java中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。所以就会导致属性值修改后,是否会对其他对象有影响.(如果是值传递的,就不会有影响,不过如果是地址传递的,修改后,之前的数据会受影响).
浅拷贝(Shallow Copy):
①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
浅拷贝的特点:
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
深拷贝(Deep Copy):
首先介绍对象图的概念。设想一下,一个类有一个对象,其成员变量中又有一个对象,该对象指向另一个对象,另一个对象又指向另一个对象,直到一个确定的实例。这就形成了对象图。那么,对于深拷贝来说,不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象图进行拷贝!
简单地说,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;而浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间。
深拷贝的特点:
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现 Cloneable
并重写 clone()
方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。
一般的引用传递实现:
在我们平时写代码时,用的最多的就是=(引用传递)来进行赋值,这就会导致赋值后,所有属性都将会重新被修改:
1 public class Student { 2 //基本数据类型 3 private int age; 4 //内部引用类型 5 private String name; 6 7 public int getAge() { 8 return age; 9 } 10 11 public void setAge(int age) { 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 @Override 24 public String toString() { 25 return "Student{hashCode:" + this.hashCode() +" "+ 26 "age=" + age + 27 ", name='" + name + '\'' + 28 '}'; 29 } 30 }
1 public class Teacher { 2 //基本数据类型 3 private int grad; 4 //内部引用类型 5 private String name; 6 //外部引用类型 7 private Student student; 8 9 public int getGrad() { 10 return grad; 11 } 12 13 public void setGrad(int grad) { 14 this.grad = grad; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public Student getStudent() { 26 return student; 27 } 28 29 public void setStudent(Student student) { 30 this.student = student; 31 } 32 33 @Override 34 public String toString() { 35 return "Teacher{hashCode:" + this.hashCode() +" "+ 36 "grad=" + grad + 37 ", name='" + name + '\'' + 38 ", student=" + student + 39 '}'; 40 } 41 }
1 public class TestRefrence { 2 public static void main(String[] args) { 3 Student student1 = new Student(); 4 student1.setName("studentA"); 5 student1.setAge(10); 6 Teacher teacher1 = new Teacher(); 7 teacher1.setGrad(1); 8 teacher1.setName("teacherA"); 9 teacher1.setStudent(student1); 10 Teacher teacher2 = teacher1; 11 teacher2.setGrad(2); 12 teacher2.setName("teacherB"); 13 Student student2 = teacher2.getStudent(); 14 student2.setName("teacherB"); 15 student2.setAge(20); 16 System.out.println("teacher1:"+teacher1.toString()); 17 System.out.println("teacher2:"+teacher2.toString()); 18 } 19 }
测试结果:
浅拷贝的实现:
实现对象拷贝的类,需要实现 Cloneable
接口,并覆写 clone()
方法。
1 public class StudentPro1 { 2 //基本数据类型 3 private int age; 4 //内部引用类型 5 private String name; 6 7 public int getAge() { 8 return age; 9 } 10 11 public void setAge(int age) { 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 @Override 24 public String toString() { 25 return "StudentPro1{studentpro1:" + this.hashCode() +" "+ 26 "age=" + age + 27 ", name='" + name + '\'' + 28 '}'; 29 } 30 }
1 public class TeacherPro1 implements Cloneable { 2 //基本数据类型 3 private int grad; 4 //内部引用类型 5 private String name; 6 //外部引用类型 7 private StudentPro1 studentPro1; 8 9 public int getGrad() { 10 return grad; 11 } 12 13 public void setGrad(int grad) { 14 this.grad = grad; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public StudentPro1 getStudentPro1() { 26 return studentPro1; 27 } 28 29 public void setStudentPro1(StudentPro1 studentPro1) { 30 this.studentPro1 = studentPro1; 31 } 32 33 @Override 34 protected Object clone() throws CloneNotSupportedException { 35 //浅拷贝 36 return super.clone(); 37 } 38 39 @Override 40 public String toString() { 41 return "TeacherPro1{teacherpro1:" + this.hashCode() + " "+ 42 "grad=" + grad + 43 ", name='" + name + '\'' + 44 ", studentPro1=" + studentPro1 + 45 '}'; 46 } 47 }
测试类:
1 public class TestShallowCopy { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 StudentPro1 studentPro1 = new StudentPro1(); 4 studentPro1.setName("studentProA"); 5 studentPro1.setAge(100); 6 TeacherPro1 teacherPro1 = new TeacherPro1(); 7 teacherPro1.setStudentPro1(studentPro1); 8 teacherPro1.setGrad(1); 9 teacherPro1.setName("teacherA"); 10 TeacherPro1 teacherPro2 = (TeacherPro1) teacherPro1.clone(); 11 teacherPro2.setName("teacherB"); 12 teacherPro2.setGrad(2); 13 StudentPro1 studentPro2 = teacherPro2.getStudentPro1(); 14 studentPro2.setName("studentProB"); 15 studentPro2.setAge(200); 16 System.out.println("teacher1:"+teacherPro1.toString()); 17 System.out.println("teacher2:"+teacherPro2.toString()); 18 19 } 20 }
测试结果:
我们发现虽然teacher1和teacher2的hashCode不一样,但是他们的内部引用类型StudentPro的hashCode是一样的.
深拷贝的实现:
对于 TeacherPro的引用类型的成员变量 StudentPro
,需要实现 Cloneable
并重写 clone()
方法。
在 TeacherPro的 clone()
方法中,需要拿到拷贝自己后产生的新的对象,然后对新的对象的引用类型再调用拷贝操作,实现对引用类型成员变量的深拷贝。
1 public class StudentPro implements Cloneable { 2 //基本数据类型 3 private int age; 4 //内部引用类型 5 private String name; 6 7 public int getAge() { 8 return age; 9 } 10 11 public void setAge(int age) { 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 @Override 24 protected Object clone() throws CloneNotSupportedException { 25 return super.clone(); 26 } 27 28 @Override 29 public String toString() { 30 return "StudentPro{studentPro:" + this.hashCode() + " "+ 31 "age=" + age + 32 ", name='" + name + '\'' + 33 '}'; 34 } 35 }
1 public class TeacherPro implements Cloneable { 2 //基本数据类型 3 private int grad; 4 //内部引用类型 5 private String name; 6 //外部引用类型 7 private Student student; 8 //外部引用类型 9 private StudentPro studentPro; 10 public int getGrad() { 11 return grad; 12 } 13 14 public void setGrad(int grad) { 15 this.grad = grad; 16 } 17 18 public String getName() { 19 return name; 20 } 21 22 public void setName(String name) { 23 this.name = name; 24 } 25 26 public Student getStudent() { 27 return student; 28 } 29 30 public void setStudent(Student student) { 31 this.student = student; 32 } 33 34 public StudentPro getStudentPro() { 35 return studentPro; 36 } 37 38 public void setStudentPro(StudentPro studentPro) { 39 this.studentPro = studentPro; 40 } 41 42 @Override 43 public String toString() { 44 return "TeacherPro{teacherPro:" + this.hashCode() + " "+ 45 "grad=" + grad + 46 ", name='" + name + '\'' + 47 ", student=" + student + 48 ", studentPro=" + studentPro + 49 '}'; 50 } 51 52 @Override 53 protected Object clone() throws CloneNotSupportedException { 54 //深拷贝 55 TeacherPro teacherPro = (TeacherPro) super.clone(); 56 teacherPro.studentPro = (StudentPro) studentPro.clone(); 57 return teacherPro; 58 } 59 }
测试类:
1 public class TestDeepCopy1 { 2 public static void main(String[] args) throws CloneNotSupportedException{ 3 StudentPro studentPro1 = new StudentPro(); 4 studentPro1.setName("studentProA"); 5 studentPro1.setAge(100); 6 TeacherPro teacherPro1 = new TeacherPro(); 7 teacherPro1.setStudentPro(studentPro1); 8 teacherPro1.setGrad(1); 9 teacherPro1.setName("teacherA"); 10 TeacherPro teacherPro2 = (TeacherPro) teacherPro1.clone(); 11 teacherPro2.setName("teacherB"); 12 teacherPro2.setGrad(2); 13 StudentPro studentPro2 = teacherPro2.getStudentPro(); 14 studentPro2.setName("studentProB"); 15 studentPro2.setAge(200); 16 System.out.println("teacher1:"+teacherPro1.toString()); 17 System.out.println("teacher2:"+teacherPro2.toString()); 18 } 19 }
测试结果:
由输出结果可见,深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。
通过对象序列化实现深拷贝:
虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。
将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。
1 public class StudentPlus implements Serializable { 2 //基本数据类型 3 private int age; 4 //内部引用类型 5 private String name; 6 7 public int getAge() { 8 return age; 9 } 10 11 public void setAge(int age) { 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 @Override 24 public String toString() { 25 return "StudentPlus{studentPlus:" + this.hashCode()+ " "+ 26 "age=" + age + 27 ", name='" + name + '\'' + 28 '}'; 29 } 30 }
1 public class TeacherPlus implements Serializable { 2 //基本数据类型 3 private int grad; 4 //内部引用类型 5 private String name; 6 //外部引用类型 7 private StudentPlus studentPlus; 8 9 public int getGrad() { 10 return grad; 11 } 12 13 public void setGrad(int grad) { 14 this.grad = grad; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public StudentPlus getStudentPlus() { 26 return studentPlus; 27 } 28 29 public void setStudentPlus(StudentPlus studentPlus) { 30 this.studentPlus = studentPlus; 31 } 32 33 @Override 34 public String toString() { 35 return "TeacherPlus{teacherPlus:" + this.hashCode() +" "+ 36 "grad=" + grad + 37 ", name='" + name + '\'' + 38 ", studentPlus=" + studentPlus + 39 '}'; 40 } 41 }
测试类:
1 public class TestDeepCopy { 2 public static void main(String[] args) { 3 StudentPlus studentPlus = new StudentPlus(); 4 studentPlus.setName("studentPlusA"); 5 studentPlus.setAge(10); 6 TeacherPlus teacherPlus1 = new TeacherPlus(); 7 teacherPlus1.setName("teacherPlusA"); 8 teacherPlus1.setGrad(1); 9 teacherPlus1.setStudentPlus(studentPlus); 10 //通过序列化方法实现深拷贝 11 ByteArrayOutputStream bos=new ByteArrayOutputStream(); 12 ObjectOutputStream oos= null; 13 try { 14 oos = new ObjectOutputStream(bos); 15 oos.writeObject(teacherPlus1); 16 oos.flush(); 17 ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); 18 TeacherPlus teacherPlus2 = (TeacherPlus) ois.readObject(); 19 teacherPlus2.setName("teacherPlusB"); 20 teacherPlus2.setGrad(2); 21 StudentPlus studentPlus2 = teacherPlus2.getStudentPlus(); 22 studentPlus2.setName("studentPlusB"); 23 studentPlus2.setAge(20); 24 System.out.println("teacherPlus1:"+teacherPlus1.toString()); 25 System.out.println("teacherPlus2:"+teacherPlus2.toString()); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } catch (ClassNotFoundException e) { 29 e.printStackTrace(); 30 } 31 } 32 }
测试结果: