浅析Java中的深拷贝和浅拷
浅析Java中的深拷贝和浅拷贝
原文链接:
http://blog.csdn.net/tounaobun/article/details/8491392
假如说你想复制一个简单变量。很简单:
- int apples = 5;
- int pears = apples;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
假设说我是一个beginner,我会这样写:
- class Student {
- private int number;
- public int getNumber() {
- return number;
- }
- public void setNumber(int number) {
- this.number = number;
- }
- }
- public class Test {
- public static void main(String args[]) {
- Student stu1 = new Student();
- stu1.setNumber(12345);
- Student stu2 = stu1;
- System.out.println("学生1:" + stu1.getNumber());
- System.out.println("学生2:" + stu2.getNumber());
- }
- }
打印结果:
- 学生1:12345
- 学生2:12345
这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?
我们试着改变stu2实例的number字段,再打印结果看看:
- stu2.setNumber(54321);
- System.out.println("学生1:" + stu1.getNumber());
- System.out.println("学生2:" + stu2.getNumber());
打印结果:
- 学生1:54321
- 学生2:54321
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:

那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
该方法的签名是:
protected native Object clone() throws CloneNotSupportedException;
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
一般步骤是(浅复制):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)
下面对上面那个方法进行改造:
- class Student implements Cloneable{
- private int number;
- public int getNumber() {
- return number;
- }
- public void setNumber(int number) {
- this.number = number;
- }
- @Override
- public Object clone() {
- Student stu = null;
- try{
- stu = (Student)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return stu;
- }
- }
- public class Test {
- public static void main(String args[]) {
- Student stu1 = new Student();
- stu1.setNumber(12345);
- Student stu2 = (Student)stu1.clone();
- System.out.println("学生1:" + stu1.getNumber());
- System.out.println("学生2:" + stu2.getNumber());
- stu2.setNumber(54321);
- System.out.println("学生1:" + stu1.getNumber());
- System.out.println("学生2:" + stu2.getNumber());
- }
- }
打印结果:
[plain] view plaincopyprint?
- 学生1:12345
- 学生2:12345
- 学生1:12345
- 学生2:54321
如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:
- System.out.println(stu1 == stu2); // false
上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):
我们在学生类里再加一个Address类。
- class Address {
- private String add;
- public String getAdd() {
- return add;
- }
- public void setAdd(String add) {
- this.add = add;
- }
- }
- class Student implements Cloneable{
- private int number;
- private Address addr;
- public Address getAddr() {
- return addr;
- }
- public void setAddr(Address addr) {
- this.addr = addr;
- }
- public int getNumber() {
- return number;
- }
- public void setNumber(int number) {
- this.number = number;
- }
- @Override
- public Object clone() {
- Student stu = null;
- try{
- stu = (Student)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return stu;
- }
- }
- public class Test {
- public static void main(String args[]) {
- Address addr = new Address();
- addr.setAdd("杭州市");
- Student stu1 = new Student();
- stu1.setNumber(123);
- stu1.setAddr(addr);
- Student stu2 = (Student)stu1.clone();
- System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
- System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
- }
- }
打印结果:
- 学生1:123,地址:杭州市
- 学生2:123,地址:杭州市
乍一看没什么问题,真的是这样吗?
我们在main方法中试着改变addr实例的地址。
- addr.setAdd("西湖区");
- System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
- System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
打印结果:
- 学生1:123,地址:杭州市
- 学生2:123,地址:杭州市
- 学生1:123,地址:西湖区
- 学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:
- package abc;
- class Address implements Cloneable {
- private String add;
- public String getAdd() {
- return add;
- }
- public void setAdd(String add) {
- this.add = add;
- }
- @Override
- public Object clone() {
- Address addr = null;
- try{
- addr = (Address)super.clone();
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- return addr;
- }
- }
- class Student implements Cloneable{
- private int number;
- private Address addr;
- public Address getAddr() {
- return addr;
- }
- public void setAddr(Address addr) {
- this.addr = addr;
- }
- public int getNumber() {
- return number;
- }
- public void setNumber(int number) {
- this.number = number;
- }
- @Override
- public Object clone() {
- Student stu = null;
- try{
- stu = (Student)super.clone(); //浅复制
- }catch(CloneNotSupportedException e) {
- e.printStackTrace();
- }
- stu.addr = (Address)addr.clone(); //深度复制
- return stu;
- }
- }
- public class Test {
- public static void main(String args[]) {
- Address addr = new Address();
- addr.setAdd("杭州市");
- Student stu1 = new Student();
- stu1.setNumber(123);
- stu1.setAddr(addr);
- Student stu2 = (Student)stu1.clone();
- System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
- System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
- addr.setAdd("西湖区");
- System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
- System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
- }
- }
打印结果:
- 学生1:123,地址:杭州市
- 学生2:123,地址:杭州市
- 学生1:123,地址:西湖区
- 学生2:123,地址:杭州市
这样结果就符合我们的想法了。
总结:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,
没有对引用指向的对象进行拷贝。
而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。
区别就在于是否对 对象中的引用变量所指向的对象进行拷贝。
最后我们可以看看API里其中一个实现了clone方法的类:
java.util.Date:
- /**
- * Return a copy of this object.
- */
- public Object clone() {
- Date d = null;
- try {
- d = (Date)super.clone();
- if (cdate != null) {
- d.cdate = (BaseCalendar.Date) cdate.clone();
- }
- } catch (CloneNotSupportedException e) {} // Won't happen
- return d;
- }
该类其实也属于深度复制。
浅析Java中的深拷贝和浅拷的更多相关文章
- 浅谈Java中的深拷贝和浅拷贝(转载)
浅谈Java中的深拷贝和浅拷贝(转载) 原文链接: http://blog.csdn.net/tounaobun/article/details/8491392 假如说你想复制一个简单变量.很简单: ...
- 浅谈Java中的深拷贝和浅拷贝
转载: 浅谈Java中的深拷贝和浅拷贝 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bool ...
- 浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 浅析Java中的访问权限控制
浅析Java中的访问权限控制 今天我们来一起了解一下Java语言中的访问权限控制.在讨论访问权限控制之前,先来讨论一下为何需要访问权限控制.考虑两个场景: 场景1:工程师A编写了一个类ClassA,但 ...
- [转载]浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- 内功心法 -- Java中的深拷贝和浅拷贝
写在前面的话:读书破万卷,编码如有神--------------------------------------------------------------------这篇博客主要来谈谈" ...
- 【转】浅析Java中的final关键字
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...
- 浅析Java中的native关键字
浅析Java中的native关键字 native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中.Java语言本身不能对操作系统底层进 ...
- 浅析 Java 中的继承和重写
浅析 Java 中的继承和重写 Java 中的构造方法不能被继承. Java 中 static 修饰的方法可以被继承,但不能被子类重写. Java 中 final 修饰方法不允许被子类重写,但是可以被 ...
随机推荐
- poj 1703 Find them, Catch them 【并查集 新写法的思路】
题目地址:http://poj.org/problem?id=1703 Sample Input 1 5 5 A 1 2 D 1 2 A 1 2 D 2 4 A 1 4 Sample Output N ...
- 吴恩达深度学习笔记(五) —— 优化算法:Mini-Batch GD、Momentum、RMSprop、Adam、学习率衰减
主要内容: 一.Mini-Batch Gradient descent 二.Momentum 四.RMSprop 五.Adam 六.优化算法性能比较 七.学习率衰减 一.Mini-Batch Grad ...
- idea ource 1.5 中不支持 switch 中存在字符串
报错内容如下: Error:(49, 20) java: -source 1.5 中不支持 switch 中存在字符串, (请使用 -source 7 或更高版本以允许 switch 中存在字符串) ...
- Nginx的访问日志配置信息详解
Nginx的访问日志可以让我们知晓用户的地址,网站的那些部分最受欢迎,以及用户浏览时间等.Nginx会把每个用户的访问日志记录到指定的日志文件中. Nginx主要有两个参数来控制 log_format ...
- springmvc文件上传的基本描述
SpringMVC的文件上传,底层也是使用的Apache的Commons-fileupload 可以分为三步: 1.导入依赖包 <!-- 文件上传的依赖 --> <dependenc ...
- 【bzoj3747】Kinoman[POI2015](线段树)
题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3747 对于这种题,考虑固定区间的右端点为r,设区间左端点为l能取得的好看值总和为a[l] ...
- MySql 创建/删除数据库
C:\Users\Mr.Black>mysql -u root -pEnter password: ****Welcome to the MySQL monitor. Commands end ...
- JNI_Z
1. ZC: 用到 VC6 ... http://blog.csdn.net/jiangwei0910410003/article/details/17465085 http://blog.csdn. ...
- javascript-DOM操作-留言板制作
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- hdu5692 dfs序线段树
这是补的知识点,按先序遍历的顺序建立dfs序,用左右两个值代表整个区间,因为dfs序最重要的特点就是子树的区间是连续的 建立线段树时,需要用重新标过的 下标来建立 #pragma comment(li ...