文章同步更新在个人博客:关于Java的Object.clone()方法与对象的深浅拷贝

引言

在某些场景中,我们需要获取到一个对象的拷贝用于某些处理。这时候就可以用到Java中的Object.clone方法进行对象复制,得到一个一模一样的新对象。但是在实际使用过程中会发现:当对象中含有可变的引用类型属性时,在复制得到的新对象对该引用类型属性内容进行修改,原始对象响应的属性内容也会发生变化,这就是"浅拷贝"的现象。关于浅拷贝,Object.clone()方法的描述也有说明:

/**
* Creates and returns a copy of this object. The precise meaning
* ...
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
*
* 上面这里已经说明了,clone()方法是浅拷贝,而不是深拷贝
*
* ...
* @return a clone of this instance.
* @exception CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;

浅拷贝

接下来用代码看看浅拷贝的效果。

1. 创建一个Person类

package com.test.objclone.shallow;

public class Person implements Cloneable {
private int age;
private String name;
private Address address; public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
} @Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} public String display() {
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}

由于clone()方法是protected修饰的,因此需要实现Cloneable接口才能调用,同时需要覆写clone()方法才能调用。

2. 创建一个Address类

package com.test.objclone.shallow;

public class Address {
private String province;
private String street; public Address(String province, String street) {
this.province = province;
this.street = street;
} public String getProvince() {
return province;
} public void setProvince(String province) {
this.province = province;
} public String getStreet() {
return street;
} public void setStreet(String street) {
this.street = street;
} @Override
public String toString() {
return "Address [province=" + province + ", street=" + street + "]";
} }

3. 浅拷贝测试

package com.test.objclone.shallow;

/**
* 浅拷贝测试
* </ul>
* @author hanxiaojun
* @version 5.0 since 2018年3月14日
*/
public class MTest {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(15, "zhangsan", new Address("四川", "天府二街")); Person clonePerson = (Person) person.clone(); System.out.println(person);
System.out.println(clonePerson); System.out.println(person.display());
System.out.println(clonePerson.display()); clonePerson.setName("wangwu");
clonePerson.setAge(20);
Address address = clonePerson.getAddress();
address.setStreet("天府四街");
System.out.println(clonePerson.display());
System.out.println(person.display()); }
}

以上程序结果如下:

com.test.objclone.shallow.Person@7ba28183
com.test.objclone.shallow.Person@69e4fede
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=20, name=wangwu, address=Address [province=四川, street=天府四街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府四街]]

可以看到:

第1、2句输出说明了原对象与新对象是两个不同的对象。

第3、4句可以看到拷贝出来的新对象与原对象内容一致

但是,接着将新对象里面的信息进行了修改,然后输出发现原对象里面的部分信息也跟着变了。仔细观察发现原对象跟着变化的只是Address部分,这就跟clone本身的浅拷贝有关系了。

浅拷贝:创建一个新对象,然后将当前对象的非静态字段复制到该对象,如果字段类型是值类型(基本类型)的,那么对该字段进行复制;如果字段是引用类型的,则只复制该字段的引用而不复制引用指向的对象。此时新对象里面的引用类型字段相当于是原始对象里面引用类型字段的一个副本,原始对象与新对象里面的引用字段指向的是同一个对象。

因此,修改clonePerson里面的address内容时,原person里面的address内容会跟着改变。

深拷贝

了解了浅拷贝,那么深拷贝是什么也就很清楚了。即将引用类型的属性内容也拷贝一份新的。

那么,实现深拷贝我这里收集到两种方式:第一种是给需要拷贝的引用类型也实现Cloneable接口并覆写clone方法;第二种则是利用序列化。接下来分别对两种方式进行演示。

深拷贝-clone方式

对于以上演示代码,利用clone方式进行深拷贝无非就是将Address类也实现Cloneable,然后对Person的clone方法进行调整。

1. Address类变动

实现Cloneable,并覆写clone方法

package com.test.objclone.deep.clone;

public class Address implements Cloneable{
private String province;
private String street; public Address(String province, String street) {
this.province = province;
this.street = street;
} @Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
} public String getProvince() {
return province;
} public void setProvince(String province) {
this.province = province;
} public String getStreet() {
return street;
} public void setStreet(String street) {
this.street = street;
} @Override
public String toString() {
return "Address [province=" + province + ", street=" + street + "]";
} }

2. Person类变动

对clone方法进行如下调整:

package com.test.objclone.deep.clone;

public class Person implements Cloneable {
private int age;
private String name;
private Address address; public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
} @Override
protected Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
//手动对address属性进行clone,并赋值给新的person对象
person.address = (Address) address.clone();
return person;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} public String display() {
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}

3. 深拷贝测试

再次执行上面的测试方法

节省篇幅,就不再贴主方法测试类了

此时,测试代码执行如下:

com.test.objclone.deep.clone.Person@69e4fede
com.test.objclone.deep.clone.Person@3918d722
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=20, name=wangwu, address=Address [province=四川, street=天府四街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]

可以看到原person对象的address内容未受到影响。

但是,这种方法的缺点就是当一个类里面有很多引用类型时,需要手动调用很多clone,而且如果引用类型内部还有引用类型时,那么代码将会很恶心,量也很大。。。

所以这种方式一般用于引用类型变量较少的时候。

对于很多引用类型,可以使用序列化对象的方式进行深拷贝。

深拷贝-序列化方式

这种方式其实就是将对象转成二进制流,然后再把二进制流反序列成一个java对象,这时候反序列化生成的对象是一个全新的对象,里面的信息与原对象一样,但是所有内容都是一份新的。

这种方式需要注意的地方主要是所有类都需要实现Serializable接口,以便进行序列化操作。

1. 序列化-反序列化对象

先看看核心代码,序列化与反序列化对象:

package com.test.objclone.deep.serialize;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class DeepClone implements Serializable{
private static final long serialVersionUID = 1L; /**
* 利用序列化和反序列化进行对象的深拷贝
* @return
* @throws Exception
*/
protected Object deepClone() throws Exception{
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject();
}
}

2. Person类的修改

主要是实现Serializable接口,让对象支持序列化。

package com.test.objclone.deep.serialize;

public class Person extends DeepClone{
private static final long serialVersionUID = 1L;
private int age;
private String name;
private Address address; public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Address getAddress() {
return address;
} public void setAddress(Address address) {
this.address = address;
} public String display() {
return "Person [age=" + age + ", name=" + name + ", address=" + address + "]";
}
}

3. Address类的修改

也需要实现Serializable接口,为了方便就继承DeepClone了。

package com.test.objclone.deep.serialize;

public class Address extends DeepClone{
private static final long serialVersionUID = 1L;
private String province;
private String street; public Address(String province, String street) {
this.province = province;
this.street = street;
} public String getProvince() {
return province;
} public void setProvince(String province) {
this.province = province;
} public String getStreet() {
return street;
} public void setStreet(String street) {
this.street = street;
} @Override
public String toString() {
return "Address [province=" + province + ", street=" + street + "]";
} }

4. 测试类

package com.test.objclone.deep.serialize;

public class MTest {
public static void main(String[] args) throws Exception {
Person person = new Person(15, "zhangsan", new Address("四川", "天府二街")); Person clonePerson = (Person) person.deepClone(); System.out.println(person);
System.out.println(clonePerson); System.out.println(person.display());
System.out.println(clonePerson.display()); clonePerson.setName("wangwu");
clonePerson.setAge(20);
Address address = clonePerson.getAddress();
address.setStreet("天府四街");
System.out.println(clonePerson.display());
System.out.println(person.display()); }
}

测试代码执行结果如下:

com.test.objclone.deep.serialize.Person@43059849
com.test.objclone.deep.serialize.Person@6a5bc8c9
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]
Person [age=20, name=wangwu, address=Address [province=四川, street=天府四街]]
Person [age=15, name=zhangsan, address=Address [province=四川, street=天府二街]]

可见,对新对象clonePerson的修改并没影响原person对象的内容。

这便是由Object.clone()引出的深拷贝与浅拷贝,学习记录一下。

参考文章

关于Java的Object.clone()方法与对象的深浅拷贝的更多相关文章

  1. Object.clone()方法与对象的深浅拷贝

    转载:[https://www.cnblogs.com/nickhan/p/8569329.html] 引言 在某些场景中,我们需要获取到一个对象的拷贝用于某些处理.这时候就可以用到Java中的Obj ...

  2. Java clone() 方法克隆对象——深拷贝与浅拷贝

    基本数据类型引用数据类型特点 1.基本数据类型的特点:直接存储在栈(stack)中的数据 2.引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里 引用数据类型在栈中存储了指针,该指 ...

  3. 详解Java中的clone方法:原型模式

    转:http://developer.51cto.com/art/201506/478985.htm clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的 ...

  4. 转:Java中的Clone()方法详解

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  5. 详解Java中的clone方法

    详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...

  6. Java中的clone方法-理解浅拷贝和深拷贝

    最近学到Java虚拟机的相关知识,更加能理解clone方法的机制了 java中的我们常常需要复制的类型有三种: 1:8种基本类型,如int,long,float等: 2:复合数据类型(数组): 3:对 ...

  7. 详解Java中的clone方法 -- 原型模式

    转自: http://blog.csdn.net/zhangjg_blog/article/details/18369201 Java中对象的创建   clone顾名思义就是复制, 在Java语言中, ...

  8. java中的clone方法

    Java中对象的创建 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  9. JAVA中的clone方法剖析

    原文出自:http://blog.csdn.net/shootyou/article/details/3945221 java中也有这么一个概念,它可以让我们很方便的"制造"出一个 ...

随机推荐

  1. WhatsApp Group vs WhatsApp Broadcast for Business

    WhatsApp Group vs WhatsApp Broadcast for Business By Iaroslav Kudritskiy If you've read our Ultimate ...

  2. php代码调试的重要性

    从去年开始做PHP,基本上有的集成环境用了一个遍,XAMPP,WAMP,phpStudy.都是部署一个环境,就在环境下的默认访问目录去创建项目运行.用PHPStorm一直没能在本地做过什么调试.要么不 ...

  3. [转帖]「知乎知识库」— 5G

    「知乎知识库」— 5G 甜草莓 https://zhuanlan.zhihu.com/p/55998832 ​ 通信 话题的优秀回答者 已关注 881 人赞同了该文章 谢 知识库 邀请~本文章是几个答 ...

  4. error: audit:backlog limit exceeded

    报错场景:telnet.ping.ftp都通的情况下,无法ssh服务器 原因:audit缓冲区设置过小,服务器默认缓冲区大小为320kb 解决办法:可通过auditctl -b 8192设定缓冲区大小 ...

  5. 【转载】SpringBoot-配置发送邮件遇到的一些问题

    前言:前一天调用163邮箱发送邮件还么有问题,今天再调用就各种发送不成功,害的我都关闭授权,还花了一毛钱短信费重新开启授权,最后百度到了一篇文章,非常贴切,在此转载下. 本人遇到的错误代码是554,邮 ...

  6. Class.getResources()和classLoader.getResources()区别

    Class.getResource(String path) path不以’/'开头时,默认是从此类所在的包下取资源: path 以’/'开头时,则是从ClassPath根下获取: package t ...

  7. LASSO回归与L1正则化 西瓜书

    LASSO回归与L1正则化 西瓜书 2018年04月23日 19:29:57 BIT_666 阅读数 2968更多 分类专栏: 机器学习 机器学习数学原理 西瓜书   版权声明:本文为博主原创文章,遵 ...

  8. zookeeper客户端KeeperErrorCode = ConnectionLoss异常问题排查历险记

    经过线报,说前方应用有异常,导致了可用性变差.咦!讨厌的异常,抛异常是程序猿最讨厌的事情之一. 经过收集异常信息如下 ​ ​ 2019-06-24 10:57:41.806 ERROR [hades- ...

  9. java都13了, 8的新特性你还没不会用吗

    前言 java13都已经来了,很多同学还停留在使用java5的东西.如果在日常开发中没有使用上java8的一些新特性或者不会用.这篇文章对你可能有帮助. lambda表达式 介绍 lambda表达式是 ...

  10. ASP .NET依赖注入理解

    ASP .NET依赖注入理解[转]:  https://www.cnblogs.com/wzk153/p/10892444.html