图片若无法显示,可至掘金查看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 方法来拷贝对象的更多相关文章

  1. 点评阿里JAVA手册之编程规约(OOP 规约 、集合处理 、并发处理 、其他)

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文难度系数为三星(★★★) 本文为第二篇 第一篇 点评阿里JAVA手 ...

  2. 点评阿里JAVA手册之MySQL数据库 (建表规约、索引规约、SQL语句、ORM映射)

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:MySQL数据库 (建表规约.索引规约.SQL语句.ORM映 ...

  3. 点评阿里JAVA手册之异常日志(异常处理 日志规约 )

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:异常处理 日志规约 本文难度系数为一星(★) 本文为第三篇 ...

  4. 阿里 Java 手册系列教程:为啥强制子类、父类变量名不同?

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 父子类变量名相同会咋样? 为啥强制子类.父类变量名不同? ...

  5. Object类clone方法的自我理解

    网上搜帖: clone()是java.lang.Object类的protected方法,实现clone方法: 1)类自身需要实现Cloneable接口 2)需重写clone()方法,最好设置修饰符mo ...

  6. Cloneable接口和Object的clone()方法

    为什么要克隆 为什么要使用克隆,这其实反映的是一个很现实的问题,假如我们有一个对象: public class SimpleObject implements Cloneable { private ...

  7. 方法object面试题分析:7JAVA中Object的clone方法详解-克隆-深克隆

    时间紧张,先记一笔,后续优化与完善.     每日一道理 翻开早已发黄的页张,试着寻找过去所留下的点点滴滴的足迹.多年前的好友似乎现在看来已变得陌生,匆忙之间,让这维持了多年的友谊变淡,找不出什么亲切 ...

  8. java基础面试题:写clone()方法时,通常都有一行代码,是什么?

    clone()方法 与new constructor()构造器创建对象不同 是克隆一个新的对象 package com.swift; public class Clone_Test { public ...

  9. 点评阿里JAVA手册之编程规约(命名风格、常量定义、代码风格、控制语句、注释规约)

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文难度系数为一星(★) 码出高效.码出质量. 代码的字里行间流淌的是 ...

随机推荐

  1. Virtual Judge HDU 1241 Oil Deposits

    八方向   深搜 #include <iostream> #include<cstdio> #include<cstdlib> #include<algori ...

  2. AcWing 789. 数的范围 二分+模板

    https://www.acwing.com/problem/content/791/ #include<bits/stdc++.h> using namespace std; ; int ...

  3. 进程管理 supervisor

    背景知识: supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启. 它是通过fork/exec的方式把这 ...

  4. chrome firefox浏览器屏蔽百度热搜

    我是原文 操作 点击拦截元素,然后选择页面元素,添加

  5. 如何处理python异常

    1.python异常有那些? window的机器如果安装了python,则直接可以在idle中查看,打开idle,按F1即可打开帮助文档,按如下路径即可查看,也可以去python官网查看这里不说明了百 ...

  6. Java-POJ1002-487-3279(含c++代码)

    Java 的读入还不熟练,解决不了空行的问题,还是只能用c++ A掉,唉~ 之后要把这个坑补掉 解决了,开心(*^▽^*)以下是AC的Java代码 以下是C++代码 #include<cstdi ...

  7. 【PAT甲级】1108 Finding Average (20分)

    题意: 输入一个正整数N(<=100),接着输入一行N组字符串,表示一个数字,如果这个数字大于1000或者小于1000或者小数点后超过两位或者压根不是数字均为非法,计算合法数字的平均数. tri ...

  8. 每天进步一点点------SOPC PIO (一)

    最近想使用Nios II里的并口PIO口进行双向操作,即需要输出的时候设置为输出方向,需要输入的时候设置为输入方向.在这期间,因为没认真仔细阅读参考文档,走了一点点的弯路.下面就简单的介绍下并行输入/ ...

  9. 任意模数 n 次剩余

    \(n\) 次剩余 你需要解方程 \(x^n\equiv k\pmod m\),其中 \(x\in [0,m-1]\). 保证解数不超过 \(C=10^6\) \(1\le n,m,k\le 10^9 ...

  10. 输入与输出 Perl 第五章

    1. chmop($line=<STDIN>) ;  #读取下一行,截掉换行符. 2. while(defined($line=<STDIN>) { print " ...