java赋值、浅拷贝和深拷贝
链接: 继续上一篇对象赋值的文章的深入学习.
值类型vs引用类型
这两个概念的区分对我们问题的研究很重要
- 在Java中,像数组、类Class、枚举Enum、Integer包装类等等,就是典型的引用类型,所以操作时一般来说采用的也是引用传递的方式
- Java的语言级基础数据类型,诸如int这些基本类型,操作时一般采取的则是值传递的方式,称它为值类型。
赋值
先定义两个类
public class Student{
private int age;
private String name;
private Major major;//学生选课
//其他代码
public class Major{
private String majorName;//选修课名称
private int majorId;//选修课id
//其他代码
}
测试赋值类
public static void main(String[] args) {
//值类型
String s1="hello";
String s2=s1;//值类型赋值,s1、s2为两个数据
s2="world";
System.out.println(s1);
System.out.println(s2);
//引用类型
Major m=new Major("java语言基础",123456789);
Student stu1=new Student(18,"张三",m);
Student stu2=stu1;//对象赋值,引用关系,并没有生成新的实际对象
System.out.println(stu1==stu2);
System.out.println(stu1);
System.out.println(stu2);
stu1.setAge(35);
Major m2=new Major("java语言基础",10000000);
stu2.setMajor(m2);
System.out.println(stu1==stu2);
System.out.println(stu2);
System.out.println(stu1);
}
输出的结果:
hello
world
true
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
true
Student{age=35, name='张三', major=Major{majorName='java语言基础', majorId=10000000}}
Student{age=35, name='张三', major=Major{majorName='java语言基础', majorId=10000000}}
可见,对象赋值(引用类型)后没有生成新的对象,二者的对象地址是一样的
即是上一篇文章我遇到的问题
浅拷贝
介绍:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
特点:
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
- 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
结构图:
深拷贝
介绍:
浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 stu2 的 Major,但是 stu1 的 Major 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。
在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
特点:
- 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
- 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
- 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
- 深拷贝相比于浅拷贝速度较慢并且花销较大。
结构图:
浅拷贝的实现
以上面的例子Student学生类实现
浅拷贝的典型实现方式是:实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。。
public class Student implements Cloneable{
private int age;
private String name;
private Major major;//学生选课
//浅拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//其他代码
}
测试类:
public class Test02 {
public static void main(String[] args) throws CloneNotSupportedException {
Major m=new Major("java语言基础",123456789);
Student stu1=new Student(18,"张三",m);
//stu1拷贝得到stu2
Student stu2= (Student) stu1.clone();
System.out.println(stu1==stu2);
System.out.println(stu1);
System.out.println(stu2);
System.out.println();
stu2.setAge(13);
m.setMajorId(1000000000);
//Major m2=new Major("java语言基础",100000000);
//stu1.setMajor(m2);
System.out.println(stu1);
System.out.println(stu2);
}
}
输出的结果:
false
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}
Student{age=13, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}
由输出的结果可见,通过 stu1.clone() 拷贝对象后得到的 stu2,和 stu1 是两个不同的对象。stu1和 stu2 的基础数据类型的修改互不影响,而引用类型 Major 修改后是会有影响的。
深拷贝的实现
虽然clone()方法可以完成对象的拷贝工作,但是注意:clone()方法默认是浅拷贝行为,就像上面的例子一样。若想实现深拷贝需覆写 clone()方法实现引用对象的深度遍历式拷贝,进行地毯式搜索。
所以对于上面的例子,如果想实现深拷贝,首先需要对更深一层次的引用类Major做改造,让其也实现Cloneable接口并重写clone()方法:
public class Major implements Cloneable{
private String majorName;//选修课名称
private int majorId;//选修课id
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//其他代码
}
其次我们还需要在顶层的调用类中重写clone方法,来调用引用类型字段的clone()方法实现深度拷贝,对应到本文那就是Student类:
public class Student implements Cloneable{
private int age;
private String name;
private Major major;//学生选课
//深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
Student student=(Student) super.clone();
student.major=(Major) major.clone(); //重要
return student;
}
//其他代码
}
这时候我们还是用上面的测试例子
此时输出结果为:
false
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}
Student{age=13, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
很明显,这时候stu1和stu2两个对象就完全独立了,不受互相的干扰。
学习文章
https://www.jianshu.com/p/94dbef2de298
github.com/hansonwang99/JavaCollection