为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象
图片若无法显示,可至掘金查看https://juejin.im/post/5d425230f265da039519d248
前言
在阿里Java开发手册中,有这么一条建议:慎用 Object 的 clone 方法来拷贝对象。对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝 。Java中的对象拷贝,有浅拷贝和深拷贝两种,如果没有搞清楚这两者的区别,那么可能会给自己的代码埋下隐患。
什么是浅拷贝和深拷贝
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
通过上面的结论,我们可以看出浅拷贝和深拷贝的区别就在于所要拷贝的对象的引用数据类型,如果是拷贝一份引用,那么这是浅拷贝,如果是新建一个对象,那么这就是深拷贝。
clone方法
在Java的Object对象中,有clone这个方法。它被声明为了 protected ,所以我们可以在其子类中使用它。这里需要注意的是,我们在子类中使用clone方法时,子类需要实现Cloneable接口,否则会抛出java.lang.CloneNotSupportedException异常。
有如下对象
如下实体类都使用了Lombok。
Address.java
@Data
public class Address {
private String address;
}
Person.java
@Data
public class Person implements Cloneable {
private String name;
private Integer age;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
浅拷贝
浅拷贝,示例代码如下:
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.setName("Happyjava");
person.setAge(33);
Address address = new Address();
address.setAddress("浙江杭州");
person.setAddress(address);
Person newPerson = (Person) person.clone();
System.out.println(person == newPerson);
System.out.println(person.getAddress() == newPerson.getAddress());
}
通过 == 比较是否是同一个对象。其运行结果如下:
false
true
说明了通过clone方法拷贝出来的对象,与原对象并不是同一个对象。而person.getAddress() == newPerson.getAddress() 的比较是true,说明了二者的引用都是指向同一个对象。这就是浅拷贝,引用类型还是指向原来的对象。
浅拷贝存在的问题
很多时候,我们拷贝一个对象,是希望完全进行深度拷贝的。浅拷贝存在的问题就是,对于原对象引用类型的属性进行修改,拷贝出来的对象也会受到影响(因为二者的引用都指向同一个对象)。如下代码:
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.setName("Happyjava");
person.setAge(33);
Address address = new Address();
address.setAddress("浙江杭州");
person.setAddress(address);
Person newPerson = (Person) person.clone();
newPerson.getAddress().setAddress("广东省深圳市");
System.out.println(person.getAddress().getAddress());
}
运行结果如下:
通过newPerson把address设置为“广东省深圳市”,person的address也变成了"广东省深圳市"。
这种情况,如果我们没有注意,是很容易造成生产事故的。
深拷贝
通过clone方法实现深拷贝,是一件比较麻烦的事情,因为我们需要手动在clone方法里拷贝引用类型。代码修改如下:
Address.java
@Data
public class Address implements Cloneable {
private String address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person.java
@Data
public class Person implements Cloneable {
private String name;
private Integer age;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
Person newPerson = (Person) super.clone();
newPerson.address = (Address) this.address.clone();
return newPerson;
}
}
通过clone方法实现深拷贝,我们需要在Person的clone方法里调用address的clone方法,并且手动设置clone出来的新的address。
再次执行上面的测试代码,运行结果如下:
通过序列化实现深拷贝
通过clone方法实现深拷贝是比较麻烦的一件事情,这里推荐大家可以通过序列化、反序列化的方式实现深拷贝。我们可以直接使用commons-lang3包的序列化、反序列工具类。
引入依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
序列化需要实现Serializable接口,Person和Address类都需要实现。测试代码如下:
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.setName("Happyjava");
person.setAge(33);
Address address = new Address();
address.setAddress("浙江杭州");
person.setAddress(address);
// 序列化
byte[] serialize = SerializationUtils.serialize(person);
// 反序列化
Person newPerson = SerializationUtils.deserialize(serialize);
System.out.println(person == newPerson);
System.out.println(person.getAddress() == newPerson.getAddress());
}
运行结果如下:
通过结果可以看出,反序列化构建出来的对象,是全新的、深度拷贝的。
总结
拷贝对象,如果直接通过clone方法进行拷贝,是很容易出现问题的。我们要清楚的知道浅拷贝和深拷贝的区别。
原创声明
本文发布于掘金号【Happyjava】。Happy的掘金地址:https://juejin.im/user/5cc2895df265da03a630ddca,Happy的个人博客:http://blog.happyjava.cn。欢迎转载,但须保留此段声明。
为什么阿里Java手册推荐慎用 Object 的 clone 方法来拷贝对象的更多相关文章
- 点评阿里JAVA手册之编程规约(OOP 规约 、集合处理 、并发处理 、其他)
下载原版阿里JAVA开发手册 [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文难度系数为三星(★★★) 本文为第二篇 第一篇 点评阿里JAVA手 ...
- 点评阿里JAVA手册之MySQL数据库 (建表规约、索引规约、SQL语句、ORM映射)
下载原版阿里JAVA开发手册 [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:MySQL数据库 (建表规约.索引规约.SQL语句.ORM映 ...
- 点评阿里JAVA手册之异常日志(异常处理 日志规约 )
下载原版阿里JAVA开发手册 [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:异常处理 日志规约 本文难度系数为一星(★) 本文为第三篇 ...
- 阿里 Java 手册系列教程:为啥强制子类、父类变量名不同?
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 父子类变量名相同会咋样? 为啥强制子类.父类变量名不同? ...
- Object类clone方法的自我理解
网上搜帖: clone()是java.lang.Object类的protected方法,实现clone方法: 1)类自身需要实现Cloneable接口 2)需重写clone()方法,最好设置修饰符mo ...
- Cloneable接口和Object的clone()方法
为什么要克隆 为什么要使用克隆,这其实反映的是一个很现实的问题,假如我们有一个对象: public class SimpleObject implements Cloneable { private ...
- 方法object面试题分析:7JAVA中Object的clone方法详解-克隆-深克隆
时间紧张,先记一笔,后续优化与完善. 每日一道理 翻开早已发黄的页张,试着寻找过去所留下的点点滴滴的足迹.多年前的好友似乎现在看来已变得陌生,匆忙之间,让这维持了多年的友谊变淡,找不出什么亲切 ...
- java基础面试题:写clone()方法时,通常都有一行代码,是什么?
clone()方法 与new constructor()构造器创建对象不同 是克隆一个新的对象 package com.swift; public class Clone_Test { public ...
- 点评阿里JAVA手册之编程规约(命名风格、常量定义、代码风格、控制语句、注释规约)
下载原版阿里JAVA开发手册 [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文难度系数为一星(★) 码出高效.码出质量. 代码的字里行间流淌的是 ...
随机推荐
- MySQL学习(五)事务隔离
文章部分图片和总结来自参考资料,侵删 概述 MySQL的可重复读隔离级别,事务T启动的时候会创建一个视图read-view,之后事务T执行期间,即使有其他事务修改了数据,事务T看到的仍然跟在启动时看到 ...
- linux 安装tar 命令
yum install -y tar 查看版本 tar --version
- 洛谷 P4555 [国家集训队]最长双回文串(Manacher)
题目链接:https://www.luogu.com.cn/problem/P4555 首先明白两个回文串,那么要使两个回文串成立,那么我们只能把$'#'$作为中间节点. 然后我们跑一边Manache ...
- eclipse报错:unable to install breakpoint in .......due to missing line number attributes
报错信息如下: 解决方案方案1.把断点都干掉,再启动.应该是代码更新后,断点位置没有代码了或位置改变了. 方案2.在Eclipse - Preferences - Java - Complier 下 ...
- Jarvis OJ - 软件密码破解-1 -Writeup
Jarvis OJ - 软件密码破解-1 -Writeup 转载请标明出处http://www.cnblogs.com/WangAoBo/p/7243801.html 记录这道题主要是想记录一下动态调 ...
- 【转载】Java中的容器讲解
转自:http://blog.csdn.net/garfielder007/article/details/52143794 Set,List,Map,Vector,ArrayList的区别 Java ...
- 关于进程,I/O模型的文章
PHP类 http://rango.swoole.com/archives/508 Node https://github.com/DoubleSpout/threadAndPackage
- Mongodb学习笔记(四)管理
一.数据导出.导入 数据导入:mongoexport ./mongoimport -d 数据库 -c 集合 文件名 数据导出:mongoimport 参数说明: -d:指明使用的库,如text -c: ...
- 【安卓逆向】ARM常见汇编指令总结
跳转指令 B 无条件跳转 BL 带链接的无条件跳转 BX 带状态切换的无条件跳转 BLX 带链接和状态的无条件跳转 存储器与寄存器交互数据指令(核心) 存储器:主存和内存 寄存器中放的数据:可以是字符 ...
- Intellij Idea2019版本激活,目前可以激活到2089,解决失效问题(非商业用途)
目录 Intellij Idea2019版本激活,目测可以激活到2089!! 前言-此教程仅用作个人学习,请勿用于商业获利,造成后果自负!!! 步骤(直接激活新下载的) 成功 注意(不想重新下载的小伙 ...