一、为什么要克隆?

使用场景: 当使用一个对象的属性时,需要进行一些修改,但是又不能直接修改该对象,此时我们就可以使用克隆来拷贝一个对象,进行操作。不然就需要new一个对象,对属性赋值。
总的来说为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数传递。
一般情况下,我们实际需要使用的是深克隆

二、如何实现克隆

  1. 对象的类实现Cloneable接口;
  2. 重写Object类的clone()方法 ;
  3. 在clone()方法中调用super.clone();

三、两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。

浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象里面包含的引用对象。

深克隆不仅拷贝对象本身,而且拷贝对象里面包含引用指向的所有对象。


浅克隆

@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Cloneable {/** 主键 */private Long id;/** 账户名称 */private String name;/** 账户详情 */private AccountDetail detail;@Overridepublic Account clone() {Account account = null;try {account = (Account) super.clone();            return account;} catch (CloneNotSupportedException e) {e.printStackTrace();}return account;}
}
@Data
@EqualsAndHashCode(callSuper = false)
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AccountDetail{/** 账户ID */private Long accountId;/** 邮箱 */private String email;
}

测试代码

 public static void main(String[] args) {// 浅克隆证明AccountDetail detail = new AccountDetail(1L, "1048791780@qq.com");Account account = new Account(1L, "小何", detail);// 克隆Account clone = (Account) account.clone();// 判断详情对对象是否相同,预期值(true)log.debug("对象是否相同:{}", clone.getDetail() == account.getDetail());log.debug("原始对象的地址:{}", System.identityHashCode(account.getDetail()));log.debug("克隆对象的地址:{}", System.identityHashCode(clone.getDetail()));clone.getDetail().setAccountId(2L);log.debug("原始对象:{}", account);log.debug("克隆对象:{}", clone);}

这里我们使用到System.identityHashCode来打印对象的地址(不完全等同地址,但是可以看做是)。hashcode通过@EqualsAndHashCode重写了。
验证结果跟预期一样。浅克隆出来的对象,修改引用对象会影响到原始对象。
由于里面的引用对象AccountDetail并没有实现克隆。
我们需要对代码改造一下,将AccountDetail也实现克隆,手动赋值。进行深度克隆。
修改完后在执行测试代码,在验证,发现达到深度克隆了。

此时,我们发现实现深度克隆,有一个麻烦之处,对象里面的引用对象也需要实现克隆,手动赋值。此时我们需要使用更方便的深度克隆方式,序列化克隆。
这种方式为:所有对象都实现克隆方法

序列化克隆(JDK 自带的字节流实现深克隆)

 /*** 使用ObjectStream序列化实现深克隆* @return Object obj*/public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {// 保存对象为字节数组try {ByteArrayOutputStream bout = new ByteArrayOutputStream();try(ObjectOutputStream out = new ObjectOutputStream(bout)) {out.writeObject(t);}// 从字节数组中读取克隆对象try(InputStream bin = new ByteArrayInputStream(bout.toByteArray())) {ObjectInputStream in = new ObjectInputStream(bin);return (T)(in.readObject());}}catch (IOException | ClassNotFoundException e){CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();e.initCause(cloneNotSupportedException);throw cloneNotSupportedException;}}

第三方工具实现

列举几个常用的工具类

 // fastJson实现克隆Account clone = JSONObject.parseObject(JSONObject.toJSONBytes(account), Account.class);//commons-beanutilsAccount cloneObject = new Account();BeanUtils.copyProperties(account,cloneObject);// 调用 apache.commons.lang 克隆对象Account a = (Account) SerializationUtils.clone(account);

疑问

  1. 在 java.lang.Object 中对 clone() 方法的限制有哪些?
  2. Java 中的克隆为什么要设计成,既要实现空接口 Cloneable,还要重写 Object 的 clone() 方法?

1、分析clone() 源码,发现注释里面有说到

  • 对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
  • 对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,但不是绝对要相等。
    这里我解读应该跟继承有关。
  • 对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

除了注释信息外,发现 clone() 是使用 native 修饰的本地方法,因此执行的性能会很高,并且它返回的类型为 Object,因此在调用克隆之后要把对象强转为目标类型才行。

protected修饰,只能同包名和子类内部调用。这样你就不能瞎鸡儿调用

2、空接口 Cloneable,看注释是JDK1.0就存在。
一个类实现Cloneable接口,表明调用Object.clone()方法进行该类实例的field-for-field(属性复制)是合法的。
在未实现Cloneable接口的实例上调用Object的clone方法会导致抛出CloneNotSupportedException异常。
由于Object.clone是native方法。我大胆猜测是历史原因。简单看一下native源码

JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))JVMWrapper("JVM_Clone");Handle obj(THREAD, JNIHandles::resolve_non_null(handle));const KlassHandle klass (THREAD, obj->klass());JvmtiVMObjectAllocEventCollector oam;#ifdef ASSERT// Just checking that the cloneable flag is set correctif (obj->is_array()) {guarantee(klass->is_cloneable(), "all arrays are cloneable");} else {guarantee(obj->is_instance(), "should be instanceOop");bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");}
#endif// Check if class of obj supports the Cloneable interface.// All arrays are considered to be cloneable (See JLS 20.1.5)if (!klass->is_cloneable()) {ResourceMark rm(THREAD);THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());}// Make shallow object copyconst int size = obj->size();oop new_obj = NULL;if (obj->is_array()) {const int length = ((arrayOop)obj())->length();new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);} else {new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);}// 4839641 (4840070): We must do an oop-atomic copy, because if another thread// is modifying a reference field in the clonee, a non-oop-atomic copy might// be suspended in the middle of copying the pointer and end up with parts// of two different pointers in the field.  Subsequent dereferences will crash.// 4846409: an oop-copy of objects with long or double fields or arrays of same// won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead// of oops.  We know objects are aligned on a minimum of an jlong boundary.// The same is true of StubRoutines::object_copy and the various oop_copy// variants, and of the code generated by the inline_native_clone intrinsic.assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,(size_t)align_object_size(size) / HeapWordsPerLong);// Clear the headernew_obj->init_mark();// Store check (mark entire object and let gc sort it out)BarrierSet* bs = Universe::heap()->barrier_set();assert(bs->has_write_region_opt(), "Barrier set does not have write_region");bs->write_region(MemRegion((HeapWord*)new_obj, size));// Caution: this involves a java upcall, so the clone should be// "gc-robust" by this stage.if (klass->has_finalizer()) {assert(obj->is_instance(), "should be instanceOop");new_obj = InstanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);}return JNIHandles::make_local(env, oop(new_obj));
JVM_END

发现其中一段代码,好熟悉的CloneNotSupportedException

  // Check if class of obj supports the Cloneable interface.// All arrays are considered to be cloneable (See JLS 20.1.5)if (!klass->is_cloneable()) {ResourceMark rm(THREAD);THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());}

再回过来看,我觉得是历史原因了。本身克隆功能就不是java写的,而且是早期版本实现的。使用频率也不高。而且也满足了任何对象都能实现克隆能力。

所以空接口Cloneable实际上就是一个标记。光实现Cloneable接口也没啥用,啥也不是。
真实的克隆功能是Object.clone()实现的。
默认都给你实现了(可能你并不需要而且也不是必要功能),所以就产生了Cloneable接口和protected修饰的限制。

思考:为什么不直接调用Object.clone

在明白克隆的实现后,我们就会想为什么不直接调用Object.clone。因为你不能直接去调用Object.clone(),该方法是protected修饰的不是public。导致你只能通过子类内部调用去实现。

  • 至于同包名内调用实现这个方式,放到跟Object同包下,我想你应该不会想不开。这条路给你堵死了。

实验一下,新建一个跟Object同包名。伪装一下,放到同包下。

package java.lang;public class Test {public static void main(String[] args) throws CloneNotSupportedException {Object o = new Object();Object clone = o.clone();}
}

直接给你报错,类加载器进行了检查,休想欺骗他。

> java.lang.SecurityException: Prohibited package name: java.lang
>            at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
  • 子类内部调用实现
    我们都会进行重写clone方法。必须改成使用public修饰。不然就会遇到跟Object.clone一样的调用困境。只能同包名和子类内部调用了。
    拿出上面例子来看。现在就知道为什么要调用super.clone了。

拓展

至于为什么没有用类似class,final这样的关键字,来个clone的关键字。
为什么不用,我认为是不想维护这么多关键字。
像goto 保留关键字,没有具体含义,这个对java而已完全就是多余的关键字。native方法有没有用到,就不得而知了

Java深克隆和浅克隆相关推荐

  1. Java深克隆和浅克隆的原理及实现

    Java中没有C++中所谓的拷贝构造函数,但是相应地提供了Cloneable接口.在Java中所有类都实现了clone()方法,因为Java中的所有类的父类Object具有clone()方法,而所有J ...

  2. 彻底理解Java深克隆和浅克隆的原理及实现

    一.为什么要克隆? 答案是:克隆的对象可能包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值.即当需要一个新的 ...

  3. java 深克隆(深拷贝)与浅克隆(拷贝)详解

    java深克隆和浅克隆 基本概念 浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅复制仅仅复制所拷贝的对象,而不复制它所引用的对 ...

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

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

  5. java对象的浅克隆和深克隆

    引言: 在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝 ...

  6. Java中的浅克隆与深克隆

    Java中的浅克隆与深克隆 一:前言 二:浅克隆与深克隆的区别 一:前言 克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个 ...

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

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

  8. 详细分析Java中的浅克隆和深克隆

    本文对浅克隆和深克隆的两种方法(不引入别的开源工具)进行了简单的代码实现(没有内部类语法),对比了浅克隆和深克隆对引用类型的影响,暂不考虑不可变类,确保初学Java者能够看懂并学会,可直接复制源代码进 ...

  9. Java中的深克隆和浅克隆的原理及三种方式实现深克隆

      本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆! 文章目录 1 克隆概述 2 深克隆实现 3 案例 3.1 测试普通clone方法--浅克隆 3.2 使用重写后的clon ...

最新文章

  1. Python_note9 Matplotlib画图 Seaborn画图
  2. sql insert and update
  3. H5应用缓存和浏览器缓存有什么区别
  4. 【Cocos2dx开发】精灵
  5. 太神奇了!使用C#实现自动核验健康码:(1)二维码识别
  6. Android - 基于Toolbar的Navigation Drawer(Material Design)
  7. 计组之中央处理器:4、硬布线控制器的原理与设计
  8. python中如何统计元组中元素的个数_python-无论元素顺序如何,获取列表中的元组数...
  9. TiFlash:并非另一个 T + 1 列存数据库
  10. JSF 与 HTML 标签的联系
  11. centos7 菜鸟第一天--输入法在哪
  12. 电脑怎么进入linux系统,Linux操作系统进入家用电脑成为发展新前景
  13. 设置背景色为渐变色 css
  14. Win10 IIS本地部署MVC网站时不能运行?
  15. fractal 分形维数 盒子维 纹理特征
  16. 2017滴滴校招 末尾0的个数(数学知识)
  17. GB28181实现对安防摄像头的直播回放控制
  18. 配置使用consol口和网线登录交换机
  19. leetcode简单之613.直线上的最近距离
  20. sourcetree的使用方法

热门文章

  1. linux ps查看进程,Linux新手入门:PS命令查看正在运行的进程
  2. 模拟哈特曼波前探测器
  3. FreeRTOS实时操作系统(七)时间片调度及RTOS的滴答定时器
  4. STM32搭建Linux开发环境之问题处理
  5. 不要让一个更名,才让TCL更有名
  6. Linux 配置 solr-4.10.3和tomcat-8.0.18
  7. 分享一个把照片变成漫画的方法
  8. 大厦智慧楼宇整体建设方案(面向IBMS的智慧大楼解决方案)
  9. OSChina 周六乱弹 ——胸会压到键盘
  10. OpenCV计算机视觉编程记录(03)---------在图像右下角插入自己的名字logo