作者:YSOcean
本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。
 
--------------------

总结

1-浅拷贝(shallow copy):创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。
 
2-深拷贝 (deep copy):创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
 
3-如何实现深拷贝?

①、让每个引用类型属性内部都继承Cloneable接口,重写clone() 方法

②、利用序列化。每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外

 
-----------------------

浅拷贝

  我们看如下这段代码:

package com.ys.test;

public class Person implements Cloneable{
public String pname;
public int page;
public Address address;
public Person() {} public Person(String pname,int page){
this.pname = pname;
this.page = page;
this.address = new Address();
} @Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
} public void setAddress(String provices,String city ){
address.setAddress(provices, city);
}
public void display(String name){
System.out.println(name+":"+"pname=" + pname + ", page=" + page +","+ address);
} public String getPname() {
return pname;
} public void setPname(String pname) {
this.pname = pname;
} public int getPage() {
return page;
} public void setPage(int page) {
this.page = page;
} }
package com.ys.test;

public class Address {
private String provices;
private String city;
public void setAddress(String provices,String city){
this.provices = provices;
this.city = city;
}
@Override
public String toString() {
return "Address [provices=" + provices + ", city=" + city + "]";
} }

  这是一个我们要进行赋值的原始类 Person。下面我们产生一个 Person 对象,并调用其 clone 方法复制一个新的对象。

  注意:调用对象的 clone 方法,必须要让类实现 Cloneable 接口,并且覆写 clone 方法。

  测试:

@Test
public void testShallowClone() throws Exception{
Person p1 = new Person("zhangsan",21);
p1.setAddress("湖北省", "武汉市");
Person p2 = (Person) p1.clone();
System.out.println("p1:"+p1);
System.out.println("p1.getPname:"+p1.getPname().hashCode()); System.out.println("p2:"+p2);
System.out.println("p2.getPname:"+p2.getPname().hashCode()); p1.display("p1");
p2.display("p2");
p2.setAddress("湖北省", "荆州市");
System.out.println("将复制之后的对象地址修改:");
p1.display("p1");
p2.display("p2");
}

  打印结果为:

  

  首先看原始类 Person 实现 Cloneable 接口,并且覆写 clone 方法,它还有三个属性,一个引用类型 String定义的 pname,一个基本类型 int定义的 page,还有一个引用类型 Address ,这是一个自定义类,这个类也包含两个属性 pprovices 和 city 。

  接着看测试内容,首先我们创建一个Person 类的对象 p1,其pname 为zhangsan,page为21,地址类 Address 两个属性为 湖北省和武汉市。接着我们调用 clone() 方法复制另一个对象 p2,接着打印这两个对象的内容。

  从第 1 行和第 3 行打印结果:

  p1:com.ys.test.Person@349319f9

  p2:com.ys.test.Person@258e4566

  可以看出这是两个不同的对象。

  从第 5 行和第 6 行打印的对象内容看,原对象 p1 和克隆出来的对象 p2 内容完全相同。

  代码中我们只是更改了克隆对象 p2 的属性 Address 为湖北省荆州市(原对象 p1 是湖北省武汉市) ,但是从第 7 行和第 8 行打印结果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被修改了。

  也就是说对象 Person 的属性 Address,经过 clone 之后,其实只是复制了其引用,他们指向的还是同一块堆内存空间,当修改其中一个对象的属性 Address,另一个也会跟着变化。

  

  浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

深拷贝

  弄清楚了浅拷贝,那么深拷贝就很容易理解了。

  深拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

  

  那么该如何实现深拷贝呢?Object 类提供的 clone 是只能实现 浅拷贝的。

如何实现深拷贝?

  深拷贝的原理我们知道了,就是要让原始对象和克隆之后的对象所具有的引用类型属性不是指向同一块堆内存,这里有三种实现思路。

  ①、让每个引用类型属性内部都重写clone() 方法

  既然引用类型不能实现深拷贝,那么我们将每个引用类型都拆分为基本类型,分别进行浅拷贝。比如上面的例子,Person 类有一个引用类型 Address(其实String 也是引用类型,但是String类型有点特殊,后面会详细讲解),我们在 Address 类内部也重写 clone 方法。如下:

  Address.class:

package com.ys.test;

public class Address implements Cloneable{
private String provices;
private String city;
public void setAddress(String provices,String city){
this.provices = provices;
this.city = city;
}
@Override
public String toString() {
return "Address [provices=" + provices + ", city=" + city + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
} }

  Person.class 的 clone() 方法:

@Override
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.address = (Address) address.clone();
return p;
}

  测试还是和上面一样,我们会发现更改了p2对象的Address属性,p1 对象的 Address 属性并没有变化。

  但是这种做法有个弊端,这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。

  ②、利用序列化

  序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们可以利用对象的序列化产生克隆对象,然后通过反序列化获取这个对象。

  注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。

//深度拷贝
public Object deepClone() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject();
}

  因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

最新文章

  1. 进程管理三大扩展工具htop
  2. linux下安装mysql
  3. Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
  4. 错误:Implicit super constructor xx() is undefined for default constructor. Must define an explicit constructor
  5. 重学OpenGL(一)----工具篇
  6. 算法导论:Trie字典树
  7. linux下vi命令大全(转载)
  8. 网络基本功(二十七):Wireshark抓包实例分析HTTP问题
  9. [LeetCode]题解(python):095-Unique Binary Search Trees II
  10. Java加入背景音乐
  11. 性能测试培训: 监控CPU之python
  12. AngularJs 4大核心
  13. HTML结构
  14. 实际项目开发需要注意的tips
  15. 腾讯云服务器SMTP ERROR: Failed to connect to server
  16. POST 调用 301 Moved Permanently 问题
  17. linux服务器部署tomcat和Nginx
  18. (字典序问题) nyoj1542-最小字符串
  19. Hadoop项目实战-用户行为分析之应用概述(二)
  20. webpack 创建vue项目过程中遇到的问题和解决方法

热门文章

  1. USACO Running Away From the Barn /// 可并堆 左偏树维护大顶堆
  2. React 生命周期 16.0以下
  3. js drag drop 收藏夹拖拽移除的简单例子
  4. matplotlib.pyplot 属性用法
  5. 微信小程序之自定义组件
  6. Batch - attrib 命令
  7. Delphi 日期函数(Day、Mon、Year、Week)使用方法描述
  8. 「题解」:毛一琛/$cow$ $subsets$
  9. NX二次开发-UFUN获得图纸页数量UF_DRAW_ask_num_drawings
  10. Core Data could not fulfill a fault