参考资料:

《深克隆和浅克隆有什么区别呢?它们是怎么实现的呢?》

目录

一、深克隆与浅克隆介绍

1、概述

2、拷贝功能的实现

二、深克隆实现方式

1、将所有对象都实现克隆

2、通过构造方法实现深克隆

3、通过字节流实现深克隆

4、使用开源工具包

补充:

1、为什么既要继承Cloneable接口也要重写clone方法

2、重写clone() 方法有哪些要求


一、深克隆与浅克隆介绍

1、概述

java中允许将一个对象赋值出一份完全一样的对象出来,称之为克隆。克隆又分浅克隆与深克隆,他们的区别在于,对于引用类型的成员变量,浅克隆复制的是引用,而深克隆复制的是对象。

浅克隆(Shadow Clone)是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对象的地址是共享给原型对象和克隆对象的。

简单来说就是浅克隆只会复制原型对象,但不会复制它所引用的对象。

        深克隆(Deep Clone)是将原型对象中的所有类型,无论是值类型还是引用类型,都复制一份给克隆对象,也就是说深克隆会把原型对象和原型对象所引用的对象,都复制一份给克隆对象。

2、拷贝功能的实现

要实现克隆则需要继承Cloneable接口,并重写Object 类中 clone()方法。

public class ProblemSolution {public static void main(String[] args)  {// 创建被赋值对象People p1 = new People();p1.setId(1);p1.setName("Java");// 克隆 p1 对象People p2;try {p2 = (People) p1.clone();System.out.println(p1.getName().equals(p2.getName()));System.out.println(p1.getName()==p2.getName());}catch (CloneNotSupportedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}class People implements Cloneable {// 属性private Integer id;private String name;/*** 重写 clone 方法* @throws CloneNotSupportedException*/@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

结果如下:

true
true

上面的代码使用了Object类自带的clone方法实现了clone,我们可以从结果中看到,两个对象中的String类型引用指向的是同一个对象,因此Object的clone方法是浅拷贝。

二、深克隆实现方式

1、将所有对象都实现克隆

        这种方式我们需要修改 People 和 Address 类,让它们都继承Cloneable接口并重写clone方法,让所有的引用对象都实现克隆,从而实现 People 类的深克隆。

class People implements Cloneable {private Address address;// 重写clone方法,内部将每一个成员变量都进行clone@Overrideprotected Object clone() throws CloneNotSupportedException {People people = (People) super.clone();people.setAddress((Address)this.address.clone()); // 引用类型克隆赋值return people;}public void setAddress(Address address) {this.address = address;       }public Address getAddress() {return address;}
}class Address implements Cloneable { private String city;// 重写clone方法@Overrideprotected Address clone() throws CloneNotSupportedException {return (Address) super.clone();}public Address(String city){this.city = city;}// 重写equals方法@Overridepublic boolean equals(Object anObject) {if (this == anObject) {return true;}if (anObject instanceof Address) {Address anotherAddress = (Address)anObject;return this.getCity().equals(anotherAddress.getCity());}return false;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}
}

将p1与其clone得到的p2进行比较。

    public static void main(String[] args)  {// 创建被赋值对象People p1 = new People();p1.setAddress(new Address("北京"));// 克隆 p1 对象People p2;try {p2 = (People) p1.clone();System.out.println(p1.getAddress().equals(p2.getAddress()));System.out.println(p1.getAddress()==p2.getAddress());}catch (CloneNotSupportedException e) {e.printStackTrace();}}

结果如下:

true
false

我们看到p1与p2中的成员变量address已经是不同的对象了,深克隆实现成功。但这么做的问题在于所有的类都要实现clone方法,过于繁琐。

2、通过构造方法实现深克隆

        《Effective Java》 中推荐使用构造器(Copy Constructor)来实现深克隆,如果构造器的参数为基本数据类型或字符串类型则直接赋值,如果是对象类型,则需要重新 new 一个对象。

    public static void main(String[] args)  {// 创建被赋值对象People p1 = new People(new Address("北京"));People p2 = new People(new Address(p1.getAddress().getCity()));System.out.println(p1.getAddress().equals(p2.getAddress()));System.out.println(p1.getAddress()==p2.getAddress());}

结果如下:

true
false

3、通过字节流实现深克隆

通过 JDK 自带的字节流实现深克隆的方式,是先将要原型对象写入到内存中的字节流,然后再从这个字节流中读出刚刚存储的信息,来作为一个新的对象返回,那么这个新对象和原型对象就不存在任何地址上的共享,这样就实现了深克隆。

简单来说,就是使用序列化存储后再读取,就可以获得一个完全一致的对象。

class Address implements Serializable{...
}class People implements Serializable{...
}class StreamClone {public static <T extends Serializable> T clone(People obj) {T cloneObj = null;try {// 写入字节流ByteArrayOutputStream bo = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bo);oos.writeObject(obj);oos.close();// 分配内存,写入原始对象,生成新对象ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流ObjectInputStream oi = new ObjectInputStream(bi);// 返回生成的新对象cloneObj = (T) oi.readObject();oi.close();} catch (Exception e) {e.printStackTrace();}return cloneObj;}
}
    public static void main(String[] args)  {// 创建被赋值对象People p1 = new People(new Address("北京"));People p2 = StreamClone.clone(p1);System.out.println(p1.getAddress().equals(p2.getAddress()));System.out.println(p1.getAddress()==p2.getAddress());}

结果如下:

true
false

需要注意的是,由于是通过字节流序列化实现的深克隆,因此每个对象必须能被序列化,必须实现 Serializable 接口,标识自己可以被序列化,否则会抛出NotSerializableException异常。

4、使用开源工具包

这里以fastjson为例,使用 JSON 工具类会先把对象转化成字符串,再从字符串转化成新的对象,因为新对象是从字符串转化而来的,因此不会和原型对象有任何的关联,这样就实现了深克隆,其他类似的 JSON 工具类实现方式也是一样的。

    public static void main(String[] args)  {// 创建被赋值对象People p1 = new People(new Address("北京"));Gson gson = new Gson();People p2 = gson.fromJson(gson.toJson(p1), People.class);System.out.println(p1.getAddress().equals(p2.getAddress()));System.out.println(p1.getAddress()==p2.getAddress());}

补充:

        1、为什么既要继承Cloneable接口也要重写clone方法

对于克隆为什么要这样设计,官方没有直接给出答案,我们只能凭借一些经验和源码文档来试着回答一下这个问题。

从源码中可以看出 Cloneable 接口在JDK 1.0 就已经存在了,因此从那个时候就已经有克隆方法了,那我们怎么来标识一个类级别对象拥有克隆方法呢?克隆虽然重要,但我们不能给每个类都默认加上克隆,这显然是不合适的,那我们能使用的手段就只有这几个了:

  • 在类上新增标识,此标识用于声明某个类拥有克隆的功能,像 final 关键字一样;
  • 使用 Java 中的注解;
  • 实现某个接口;
  • 继承某个类。

先说第一个,为了一个重要但不常用的克隆功能, 单独新增一个类标识,这显然不合适;再说第二个,因为克隆功能出现的比较早,那时候还没有注解功能,因此也不能使用;第三点基本满足我们的需求,第四点和第一点比较类似,为了一个克隆功能需要牺牲一个基类,并且 Java 只能单继承,因此这个方案也不合适。采用排除法,无疑使用实现接口的方式是那时最合理的方案了,而且在 Java 语言中一个类可以实现多个接口。

那为什么要在 Object 中添加一个 clone() 方法呢?

因为 clone() 方法对时间和效率的要求很高,因此最好能有 JVM 的直接支持,既然要 JVM 直接支持,就要找一个 API 来把这个方法暴露出来才行,最直接的做法就是把它放入到一个所有类的基类 Object 中,这样所有类就可以很方便地调用到了。

2、重写clone() 方法有哪些要求

首先看下源码,clone()是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行。虽然我们看不到clone()方法的具体实现,但我们可以从注释中看出一些信息。

/*** Creates and returns a copy of this object.  The precise meaning* of "copy" may depend on the class of the object. The general* intent is that, for any object {@code x}, the expression:* <blockquote>* <pre>* x.clone() != x</pre></blockquote>* will be true, and that the expression:* <blockquote>* <pre>* x.clone().getClass() == x.getClass()</pre></blockquote>* will be {@code true}, but these are not absolute requirements.* While it is typically the case that:* <blockquote>* <pre>* x.clone().equals(x)</pre></blockquote>* will be {@code true}, this is not an absolute requirement.* <p>* By convention, the returned object should be obtained by calling* {@code super.clone}.  If a class and all of its superclasses (except* {@code Object}) obey this convention, it will be the case that* {@code x.clone().getClass() == x.getClass()}.* <p>* By convention, the object returned by this method should be independent* of this object (which is being cloned).  To achieve this independence,* it may be necessary to modify one or more fields of the object returned* by {@code super.clone} before returning it.  Typically, this means* copying any mutable objects that comprise the internal "deep structure"* of the object being cloned and replacing the references to these* objects with references to the copies.  If a class contains only* primitive fields or references to immutable objects, then it is usually* the case that no fields in the object returned by {@code super.clone}* need to be modified.* <p>* ......*/
protected native Object clone() throws CloneNotSupportedException;

从注释中可以看出:

(1)对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;

(2)对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回true,因为克隆对象与原对象的类型是一样的;

(3)对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

Java8之深克隆与浅克隆相关推荐

  1. 我不知道的事——深克隆和浅克隆

    推荐一部好电影<致命魔术>.(此处为植入广告)        推荐理由:涉及人性.画面不错,剧情跌宕,亦魔亦幻(此处的"魔"为魔术的"魔").虽然女 ...

  2. 深克隆与浅克隆的区别

    深克隆与浅克隆之间的区别 深克隆:用递归复制了对象的所有层级 浅克隆:浅复制只复制一层对象的属性 浅克隆 浅克隆是指在克隆对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进 ...

  3. Javascript深克隆和浅克隆

    深克隆和浅克隆 浅克隆:只需要拷贝对象中的原始类型值,引用类型拷贝的仍然是地址 深克隆:无论是原始类型还是引用类型都会进行拷贝,包括引用类型中还有其他引用类型的质. <script>let ...

  4. 深克隆和浅克隆的区别

    克隆 1.简介 在Java中对象的克隆有深克隆和浅克隆之分.有这种区分的原因是Java中分为基本数据类型和引用数据类型,对于不同的数据类型在内存中的存储的区域是不同的.基本数据类型存储在栈中,引用数据 ...

  5. 浅析Java中的深克隆和浅克隆

    说实话,目前为止还没在项目中遇到过关于Java深克隆和浅克隆的场景.今天手抖戳开了花呗账单,双十二败家的战绩真是惨不忍睹,若能在我的客户端"篡改"下账单金额,那该(简)有(止)多( ...

  6. JS 中的 assign 方法究竟是 “深克隆” 还是 “浅克隆”?

    先说一下答案:assign做的是浅克隆. "第一层是深克隆,下面的每一层做的是浅克隆" 这种说法其实是错误的.    在我们去探究assign是深克隆还是浅克隆之前,我们必须先明白 ...

  7. 原型模式以及深克隆和浅克隆

    原型模式的好处 创建比较复杂的对象,无需考虑过程,简化了创建对象的创建过程,同时也能提 高效率 原型模式分为浅克隆和深克隆,不同点 以我目前的理解:深克隆和浅克隆对于我们对象内的引用对象的克隆不一样 ...

  8. java clone() 方法详解及深克隆与浅克隆

    概述 clone 翻译过来就是 克隆,顾名思义就是创造一个一模一样的事物.Java 代码中 clone() 方法是 Object 方法,而 Object 又是所有类的父类,也就是说所有 java 对象 ...

  9. 深克隆和浅克隆有什么区别?它的实现方式有哪些?

    浅克隆(Shadow Clone) 是把原型对象中成员变量为值类型的属性都复制给克隆对象,把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,也就是原型对象中如果有成员变量为引用对象,则此引用对 ...

  10. vue数组的深克隆和浅克隆

    在开发过程中,前端获取到后端的数据之后,通常直接复制后就使用,比如: var _data = res.data; //浅克隆 data是一个数组 这就是浅克隆的写法,因为后端返回的data是一个数组, ...

最新文章

  1. c程序设计语言中printf,编程序 用getchar函数读入两个字符c1和c2,然后分别用putchar和printf函数输出这两个字符。请问...
  2. 【Android 安装包优化】p7zip 源码交叉编译 Android 平台可执行程序 ( 下载 p7zip 源码 | 交叉编译 Android 中使用 7z 可执行程序 )
  3. PHP的autoload自动加载机制使用说明
  4. 编码时的一些普适原则
  5. 响应式系统reactive system初探
  6. angluar cdk_零分钟即可在容器开发套件(CDK)上实现云运营
  7. spring----IOC知识点
  8. Java并发包基石-AQS详解
  9. python实现api server,初学python,准备学习做个restful api server,现在有些困惑
  10. 1.4 php编译安装-安装顺序在最后
  11. 不一样的Office 365之 —— 使用Delve查看热门文档
  12. 工作4年,我从阿里巴巴辞职到了国企
  13. java计算机毕业设计ssm+vue高校科研管理系统
  14. 【技巧帖】关于Mac如何内录电脑内部声音
  15. [异步图书].Python机器学习:预测分析核心算法.pdf
  16. pdf加水印怎么加?五个步骤非常简单
  17. Java Swing实现高仿电脑版微信
  18. 通过跟踪源码证明在Java中通过执行Start()方法创建线程
  19. 【关于3D-R2N2的配置和使用】
  20. 计算机编码二进制0001,二进制学习01(二进制,进制运算,数据宽度,无符号位有符号位编码规则)...

热门文章

  1. android电视+dlna,如何开启电视dlna功能(最简单最实用的多屏互动)
  2. 清华大学计算机专业辅修课程,清华大学计算机应用专业-辅修专业
  3. jQuery根据纬度经度查看地图
  4. 「Android高级工程师」BAT大厂面试基础题集合-下 Github标星6.5K
  5. 传奇服务端如何添加地图
  6. 每天一句英语(有道)
  7. Unity镜头光晕模拟开源库
  8. 游戏因为音效而变得触动人心
  9. 动态平衡网格交易_网格交易 套利:期货经典书籍
  10. 12306 验证码识别源码