图片若无法显示,可至掘金查看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. C++-POJ1067-取石子游戏

    //(ak,bk)=([k*(1+sqrt(5))/2],[k*(1+sqrt(5))/2]+k)=(ak,ak+k) #include <cstdio> double sqrt5=2.2 ...

  2. Go网络编程UDP

    package main import ( "fmt" "net" "strings" ) // UDP server func main( ...

  3. 解决mailx发邮件报错:esmtp-server: 504 5.7.4 Unrecognized authentication type [HK2PR02CA0167.apcprd02.prod.outlook.com] "/root/dead.letter" 11/302 . . . message not sent.

    报错信息: esmtp-server: 504 5.7.4 Unrecognized authentication type [HK2PR02CA0167.apcprd02.prod.outlook. ...

  4. 【Python】【爬虫】爬取酷狗音乐网络红歌榜

    原理:我的上篇博客 import requests import time from bs4 import BeautifulSoup def get_html(url): ''' 获得 HTML ' ...

  5. SARS病毒

    每一道题目皆是一处美丽的风景: 何为科技的力量和程序的思维哦,在这暑假的编程之路上,我要好好地体验一番来嘞! 数学规律是:f(n)=2^(n-1)+4^(n-1).     //递推分析可得!具体过程 ...

  6. SSI注入漏洞

    简介 SSI是英文Server Side Includes的缩写,翻译成中文就是服务器端包含的意思.从技术角度上说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针.SSI具有强大的功能,只 ...

  7. 揭秘jQuery-选择器

    先看代码: $(“li”)只选择第一个无序列表中的一个li元素,而不会选择另一个无序列表中的li元素 <!DOCTYPE html> <html> <head> & ...

  8. python lib timeit 测试运行时间

    目录 1. 简介 1.1. python interface 2. 案例 2.1. timeit() /repeat() 2.2. timer() 1. 简介 27.5. timeit - Measu ...

  9. mybatis--Spring整合mybatis

    今天学习了mybatis整合Spring开发,做了一个mybatis+spring的小实例 (1)首先,创建数据库my,并在数据库my中创建表user create database my; use ...

  10. Vue.js 学习入门:介绍及安装

    Vue.js 是什么? Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层 ...