天亮教育

 找回密码
 立即注册
搜索
查看: 625|回复: 2

浅谈Java中的深拷贝和浅拷贝

[复制链接]

23

主题

24

帖子

108

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
108
发表于 2017-7-6 23:19:09 | 显示全部楼层 |阅读模式
原文链接:
假如说你想复制一个简单变量。很简单:
  1. int apples = 5;
  2. int pears = apples;
复制代码
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。假设说我是一个beginner,我会这样写:

  1. class Student {
  2.         private int number;

  3.         public int getNumber() {
  4.                 return number;
  5.         }

  6.         public void setNumber(int number) {
  7.                 this.number = number;
  8.         }
  9.         
  10. }
  11. public class Test {
  12.         
  13.         public static void main(String args[]) {
  14.                
  15.                 Student stu1 = new Student();
  16.                 stu1.setNumber(12345);
  17.                 Student stu2 = stu1;
  18.                
  19.                 System.out.println("学生1:" + stu1.getNumber());
  20.                 System.out.println("学生2:" + stu2.getNumber());
  21.         }
  22. }
复制代码
打印结果:

学生1:12345
学生2:12345

这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 =stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:
  1. stu2.setNumber(54321);
  2.         
  3.                 System.out.println("学生1:" + stu1.getNumber());
  4.                 System.out.println("学生2:" + stu2.getNumber());
  5. 打印结果:

  6. 学生1:54321
  7. 学生2:54321
复制代码
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1stu2指向内存堆中同一个对象。


那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
该方法的签名是:
protected native Object clone() throwsCloneNotSupportedException;

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。

一般步骤是(浅复制):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)
下面对上面那个方法进行改造:
  1. class Student implements Cloneable{
  2.         private int number;

  3.         public int getNumber() {
  4.                 return number;
  5.         }

  6.         public void setNumber(int number) {
  7.                 this.number = number;
  8.         }
  9.         
  10.         @Override
  11.         public Object clone() {
  12.                 Student stu = null;
  13.                 try{
  14.                         stu = (Student)super.clone();
  15.                 }catch(CloneNotSupportedException e) {
  16.                         e.printStackTrace();
  17.                 }
  18.                 return stu;
  19.         }
  20. }
  21. public class Test {
  22.         
  23.         public static void main(String args[]) {
  24.                
  25.                 Student stu1 = new Student();
  26.                 stu1.setNumber(12345);
  27.                 Student stu2 = (Student)stu1.clone();
  28.                
  29.                 System.out.println("学生1:" + stu1.getNumber());
  30.                 System.out.println("学生2:" + stu2.getNumber());
  31.                
  32.                 stu2.setNumber(54321);
  33.         
  34.                 System.out.println("学生1:" + stu1.getNumber());
  35.                 System.out.println("学生2:" + stu2.getNumber());
  36.         }
  37. }
复制代码
打印结果:

学生1:12345
学生2:12345
学生1:12345
学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:
  1. 1.        System.out.println(stu1 == stu2); // false
复制代码
上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):我们在学生类里再加一个Address类。
  1. class Address  {
  2.         private String add;

  3.         public String getAdd() {
  4.                 return add;
  5.         }

  6.         public void setAdd(String add) {
  7.                 this.add = add;
  8.         }
  9.         
  10. }

  11. class Student implements Cloneable{
  12.         private int number;

  13.         private Address addr;
  14.         
  15.         public Address getAddr() {
  16.                 return addr;
  17.         }

  18.         public void setAddr(Address addr) {
  19.                 this.addr = addr;
  20.         }

  21.         public int getNumber() {
  22.                 return number;
  23.         }

  24.         public void setNumber(int number) {
  25.                 this.number = number;
  26.         }
  27.         
  28.         @Override
  29.         public Object clone() {
  30.                 Student stu = null;
  31.                 try{
  32.                         stu = (Student)super.clone();
  33.                 }catch(CloneNotSupportedException e) {
  34.                         e.printStackTrace();
  35.                 }
  36.                 return stu;
  37.         }
  38. }
  39. public class Test {
  40.         
  41.         public static void main(String args[]) {
  42.                
  43.                 Address addr = new Address();
  44.                 addr.setAdd("杭州市");
  45.                 Student stu1 = new Student();
  46.                 stu1.setNumber(123);
  47.                 stu1.setAddr(addr);
  48.                
  49.                 Student stu2 = (Student)stu1.clone();
  50.                
  51.                 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  52.                 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  53.         }
  54. }
复制代码
打印结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市

乍一看没什么问题,真的是这样吗?
我们在main方法中试着改变addr实例的地址。
  1. addr.setAdd("西湖区");
  2. System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  3. System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());

  4. 打印结果:

  5. 学生1:123,地址:杭州市
  6. 学生2:123,地址:杭州市
  7. 学生1:123,地址:西湖区
  8. 学生2:123,地址:西湖区
复制代码
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

  1. package abc;

  2. class Address implements Cloneable {
  3.         private String add;

  4.         public String getAdd() {
  5.                 return add;
  6.         }

  7.         public void setAdd(String add) {
  8.                 this.add = add;
  9.         }
  10.         
  11.         @Override
  12.         public Object clone() {
  13.                 Address addr = null;
  14.                 try{
  15.                         addr = (Address)super.clone();
  16.                 }catch(CloneNotSupportedException e) {
  17.                         e.printStackTrace();
  18.                 }
  19.                 return addr;
  20.         }
  21. }

  22. class Student implements Cloneable{
  23.         private int number;

  24.         private Address addr;
  25.         
  26.         public Address getAddr() {
  27.                 return addr;
  28.         }

  29.         public void setAddr(Address addr) {
  30.                 this.addr = addr;
  31.         }

  32.         public int getNumber() {
  33.                 return number;
  34.         }

  35.         public void setNumber(int number) {
  36.                 this.number = number;
  37.         }
  38.         
  39.         @Override
  40.         public Object clone() {
  41.                 Student stu = null;
  42.                 try{
  43.                         stu = (Student)super.clone();        //浅复制
  44.                 }catch(CloneNotSupportedException e) {
  45.                         e.printStackTrace();
  46.                 }
  47.                 stu.addr = (Address)addr.clone();        //深度复制
  48.                 return stu;
  49.         }
  50. }
  51. public class Test {
  52.         
  53.         public static void main(String args[]) {
  54.                
  55.                 Address addr = new Address();
  56.                 addr.setAdd("杭州市");
  57.                 Student stu1 = new Student();
  58.                 stu1.setNumber(123);
  59.                 stu1.setAddr(addr);
  60.                
  61.                 Student stu2 = (Student)stu1.clone();
  62.                
  63.                 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  64.                 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  65.                
  66.                 addr.setAdd("西湖区");
  67.                
  68.                 System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
  69.                 System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
  70.         }
  71. }
复制代码
打印结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市
这样结果就符合我们的想法了。
总结:浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝,
没有对引用指向的对象进行拷贝。
而深拷贝是指在拷贝对象时,同时会对引用指向的对象进行拷贝。
区别就在于是否对  对象中的引用变量所指向的对象进行拷贝。

最后我们可以看看API里其中一个实现了clone方法的类:
  1. java.util.Date:

  2.     /**
  3.      * Return a copy of this object.
  4.      */
  5.     public Object clone() {
  6.         Date d = null;
  7.         try {
  8.             d = (Date)super.clone();
  9.             if (cdate != null) {
  10.                 d.cdate = (BaseCalendar.Date) cdate.clone();
  11.             }
  12.         } catch (CloneNotSupportedException e) {} // Won't happen
  13.         return d;
  14.     }
复制代码
该类其实也属于深度复制。



回复

使用道具 举报

0

主题

33

帖子

81

积分

注册会员

Rank: 2

积分
81
发表于 2017-8-4 16:21:44 | 显示全部楼层
回复 支持 反对

使用道具 举报

0

主题

21

帖子

52

积分

注册会员

Rank: 2

积分
52
发表于 2017-8-4 17:46:22 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|  

GMT+8, 2019-4-25 02:59 , Processed in 0.055315 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表