深拷贝&浅拷贝
Java中变量有两种类型:基本类型和引用类型
基本类型的变量保存原始值,即它代表的值就是数值本身 引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置
注:String类型通过常量赋值时相当于基本数据类型,通过new关键字创建对象时便是引用数据类型
浅拷贝
1. 浅拷贝介绍
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
2. 浅拷贝特点
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。 (2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
3. 浅拷贝的实现
public class Subject { private String name; public Subject(String name) { this.name = name; } public String toString() { return String.format("[Subject: %d, name: %s]", this.hashCode(), this.name); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Student implements Cloneable{ //基础数据类型 private int age; private String name; //引用类型 private Subject subject; public Student(int age,String name,Subject subject){ this.age=age; this.name=name; this.subject=subject; } /** * 浅拷贝,重写clone() * @return */ public Object clone(){ try { //直接调用父类的clone()方法 return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } public String toString(){ return String.format("{Student:%d, subject:%s, name:%s, age:%d}",this.hashCode(),this.subject.toString(),this.name,this.age); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } }
测试
public class Test { public static void main(String[] args) { Subject subjectA=new Subject("yuwen"); Student studentA=new Student(10,"stuA",subjectA); Student studentB= (Student) studentA.clone(); studentB.setAge(12); studentB.setName("stuB"); Subject subjectB=studentB.getSubject(); subjectB.setName("shuxue"); System.out.println("studentA: "+studentA.toString()); System.out.println("studentB: "+studentB.toString()); } }
结果
studentA: {Student:1896277646, subject:[Subject: 2128227771, name: shuxue], name:stuA, age:10} studentB: {Student:396180261, subject:[Subject: 2128227771, name: shuxue], name:stuB, age:12}
1.深拷贝介绍
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
2. 深拷贝特点
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。 (2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。 (3) 对于有多层对象的,每个对象都需要实现 Cloneable
并重写 clone()
方法,进而实现了对象的串行层层拷贝。 (4) 深拷贝相比于浅拷贝速度较慢并且花销较大。
3.深拷贝的实现
public class Subject implements Cloneable{ private String name; public Subject(String name) { this.name = name; } /** * 重写clone(),每个对象都调用父类的clone() 方法 * @return */ public Object clone(){ try { return super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } public String toString() { return String.format("[Subject: %d, name: %s]", this.hashCode(), this.name); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Student implements Cloneable{ //基础数据类型 private int age; private String name; //引用类型 private Subject subject; public Student(int age, String name, Subject subject){ this.age=age; this.name=name; this.subject=subject; } /** * 深拷贝,重写clone(),每个引用类型的变量都调用 各自重写的clone() * @return */ public Object clone(){ Student student=null; try { student= (Student) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } student.subject= (Subject) subject.clone(); return student; } public String toString(){ return String.format("{Student:%d, subject:%s, name:%s, age:%d}",this.hashCode(),this.subject.toString(),this.name,this.age); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } }
测试
public class Test { public static void main(String[] args) { Subject subjectA= new Subject("yuwen"); Student studentA=new Student(10,"stuA",subjectA); Student studentB= (Student) studentA.clone(); studentB.setAge(12); studentB.setName("stuB"); Subject subjectB=studentB.getSubject(); subjectB.setName("shuxue"); System.out.println("studentA: "+studentA.toString()); System.out.println("studentB: "+studentB.toString()); } }
结果
studentA: {Student:1896277646, subject:[Subject: 2128227771, name: yuwen], name:stuA, age:10} studentB: {Student:396180261, subject:[Subject: 625576447, name: shuxue], name:stuB, age:12}
深拷贝后,不管是基础数据类型还是引用类型的成员变量,修改其值都不会相互造成影响。
关于clone()
JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。
和 new 的区别
共同点:都是分配内存,对象都是指向不同的内存地址 不同点:new
创建一个对象,clone
复制一个对象。new
是返回的新对象,而调用clone()
方法时,拷贝对象已经包含了一些原来对象的信息,而不是对象的初始信息
在Object
类的clone()
是一个native
方法,native
方法的效率一般来说都是远高于Java中的非native
方法。这也解释了为 什么要用Object
中clone()
方法而不是先new
一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone
功能
为什么要实现Cloneable
接口
Cloneable
接口是不包含任何方法的,其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对 Object
类中clone()
方法的,如果实现clone()
方法的类没有实现Cloneable接口,并调用了Object
的clone()
方法(也就是调用了 super.clone()
方法),那么Object
的clone()
方法就会抛出CloneNotSupportedException
异常
浅拷贝
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝
深拷贝
对基本数据类型进行值传递,对引用数据类型,会对引用指向的对象进行拷贝,此为深拷贝。也就是在clone()
方法对其内的引用类型的变量再进行一次 clone()
参考