文章同步更新在个人博客:关于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. tomcat8.5.37设置虚拟目录

    之前做的solr和tomcat是放在tomcat的webapps下 solr7.2.1+tomcat8.5.37+jdk8安装配置 现在就是放在其他,通过tomcat的server.xml配置虚拟目录 ...

  2. 【AtCoder】AGC011

    AGC011 A - Airport Bus 大意:有N个人,每个人只能在\([T_i,T_i +K]\)这段区间乘车,每辆车安排C人,问最少安排几辆车 直接扫,遇到一个没有车的在\(T_i +K\) ...

  3. mysql 查询最佳实践

    (1)负向条件查询不能使用索引 select * from order where status!=0 and stauts!=1 not in/not exists都不是好习惯 (2)前导模糊查询不 ...

  4. java包装类的缓存机制(转)

    出处: java包装类的缓存机制 java 包装类的缓存机制,是在Java 5中引入的一个有助于节省内存.提高性能的功能,只有在自动装箱时有效 Integer包装类 举个栗子: Integer a = ...

  5. vc 网络编程(socket)

    在网上找了很多的资料,现将这些资料整合起来,详细介绍一下VC下的socket编程,并提供一个服务器客户端具体的实例.希望对您有所帮助 一.原理部分 (个人觉得这篇写的可以,所以转与此,原文地址:htt ...

  6. Linux下mysql创建用户并设置权限,设置远程连接

    为了安全考虑,OneinStack仅允许云主机本机(localhost)连接数据库,如果需要远程连接数据库,需要如下操作:打开iptables 3306端口 # iptables -I INPUT 4 ...

  7. Oracle 以及 PLSQL安装

    今天重装系统遇到oracle 安装的问题咯 ,oracle安装过程中很多疑难杂症咯 1如果之前装过,记得去删除注册表的Oracle 相关的文件 ,请百度有很多教程咯 2这个必须要勾选的!因为的是11g ...

  8. 一次解决黑帽SEO的经历

    最近有个朋友跟我说他的网站被黑了,百度快照里显示的是另一个网站,如: 于是查找了些资料,终于找到了问题所在,记录如下: 关于黑帽SEO1.暗链:其实“暗链”就是看不见的网站链接,“暗链”在网站中的链接 ...

  9. [转载]torch参数:torch.backends.cudnn.benchmark的意义

    [转载]torch参数:torch.backends.cudnn.benchmark的意义 来源:https://zhuanlan.zhihu.com/p/73711222 完整版请看原文,这里只截取 ...

  10. Android应用市场App发布

    来自知乎 Android应用市场App发布说到官方渠道,不得不说一些主要的大市场了,如:360.小米.应用宝.91.安卓.百度.豌豆荚.安智.现在我来一一说它们的一些简单特点. 1,360 (1)当天 ...