Java中的clone,sallow copy和deep copy的区别解析
更新时间:2026年02月27日 09:10:29 作者:杨DaB
本文给大家介绍了Java中的clone,sallow copy和deep copy的区别解析,介绍了克隆的概念和作用,以及浅克隆和深克隆的区别,感兴趣的朋友跟随小编一起看看吧
克隆的概念和作用
- 方法需要 return 引用类型,但又不希望自己本身 持有引用类型的对象被修改。
- 程序之间方法的调用时参数的传递。有些场景为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数传递。
sallow copy和deep copy的区别
sallow copy(浅克隆)
- 复制基本类型的属性;
- 引用类型的属性复制,
- 复制栈中的变量 和 变量指向堆内存中的对象的指针,
- 不复制堆内存中的对象。
两个对象使用的引用类型的属性会互相影响

deep copy(深克隆)
- 复制基本类型的属性;
- 引用类型的属性复制,
- 复制栈中的变量;
- 赋值变量指向堆内存中的对象的指针和堆内存中的对象。
- 等同于重新创建一个一模一样的对象,并复制里面的属性(数据内容);
- 测试的时候可以结合
==和equal进行学习,==比较的是基本数据类型,- A1=A1:true,0x006E != x006ER:false
- A1.equals(A1):true,0x006E .equals(x006ER):true

如何实现deep copy
- 实现
Cloneable接口,重写clone()方法。 - 不实现
Cloneable接口,会报CloneNotSupportedException异常。 Object的clone()方法是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。
方法1、对象的属性的Class 也实现 Cloneable 接口,克隆对象的同时也克隆属性。
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
如下:
- DPerson 类实现 Cloneable接口,重写clone()方法,将对象 和属性全部clone;
- DPerson 引用的对象Food类 实现Cloneable接口,重写clone()方法;
- 在主程序中调用DPerson 中的clone()方法,更改克隆后的对象内的属性进行测试
/**
* 测试克隆
*/
public class TestManalDeepClone {
public static void main(String[] args) throws Exception {
DPerson p1 = new DPerson(1, "AKA000", new DFood("米饭"));//创建Person 对象 p1
DPerson p2 = (DPerson)p1.clone();//克隆p1
p2.setName("AKA121");//修改p2的name属性
p2.getFood().setName("面条");//修改p2的自定义引用类型 food 属性
System.out.println(p1);//修改p2的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性也随之改变,说明p2的food属性,只拷贝了引用,没有拷贝food对象
System.out.println(p2);
}
}
class DPerson implements Cloneable {
private int pid;
private String name;
private DFood food;
public DPerson(int pid, String name, DFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
DPerson p = (DPerson)super.clone();
p.setFood((DFood)p.getFood().clone());
return p;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public DFood getFood() {
return food;
}
public void setFood(DFood food) {
this.food = food;
}
}
class DFood implements Cloneable{
private String name;
public DFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}方法2、结合序列化(JDK java.io.Serializable 接口、JSON格式、XML格式等),完成深拷贝
手写一个方法 cloneBySerizalizable(),使用 ByteStream流和ObjectSteam流实现;
/**
* 通过序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}如下:
- 整体思想:将数据序列化成为字节流,再反序列化为数据行程对象的clone,做出一个全新的、与原始对象完全独立的对象。;
- 编写
cloneBySerializable()方法,创建一个Object对象,设置为成员变量初始化为null,做传输使用; - 创建
ByteArrayOutputStream容器,用于接收和存储后续写入的字节数据 - 创建一个对象输出流
ObjectOutputStream oos = new ObjectOutputStream(baos);,包装在ByteArrayOutputStream之上,不能独立存在,必须依赖一个底层的输出流,指定数据最终的去向。 - 使用
writeObject方法,将当前对象 (this) 序列化为字节流。这些字节数据被写入到ByteArrayOutputStream这个内存容器中。 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());将对象反序列化会bais中,在通过ObjectInputStream进行读取,ObjectInputStream必须包装在ByteArrayInputStream之中;- 通过
readObject方法,从字节流中读取数据并反序列化,最终创建出一个全新的对象。 - 在主程序中调用DPerson 中的
cloneBySerializable()方法,更改克隆后的对象内的属性进行测试
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class TestSeriazableClone {
public static void main(String[] args) {
SPerson p1 = new SPerson(1, "AKA000", new SFood("米饭"));//创建 SPerson 对象 p1
SPerson p2 = (SPerson)p1.cloneBySerializable();//克隆 p1
p2.setName("AKA001");//修改 p2 的 name 属性
p2.getFood().setName("面条");//修改 p2 的自定义引用类型 food 属性
System.out.println(p1);//修改 p2 的自定义引用类型 food 属性被改变,p1的自定义引用类型 food 属性未随之改变,说明p2的food属性,只拷贝了引用和 food 对象
System.out.println(p2);
}
}
class SPerson implements Cloneable, Serializable {
private static final long serialVersionUID = -7710144514831611031L;
private int pid;
private String name;
private SFood food;
public SPerson(int pid, String name, SFood food) {
this.pid = pid;
this.name = name;
this.food = food;
System.out.println("Person constructor call");
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 通过序列化完成克隆
* @return
*/
public Object cloneBySerializable() {
Object obj = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return obj;
}
@Override
public String toString() {
return "Person [pid:"+pid+", name:"+name+", food:"+food.getName()+"]";
}
public SFood getFood() {
return food;
}
public void setFood(SFood food) {
this.food = food;
}
}
class SFood implements Serializable {
private static final long serialVersionUID = -3443815804346831432L;
private String name;
public SFood(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}到此这篇关于Java中的clone,sallow copy和deep copy的区别解析的文章就介绍到这了,更多相关java clone,sallow copy和deep copy区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java使用 try-with-resources 实现自动关闭资源的方法
这篇文章主要介绍了Java使用 try-with-resources 实现自动关闭资源的方法,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-06-06
Mybatis分页插件PageHelper的配置和简单使用方法(推荐)
在使用Java Spring开发的时候,Mybatis算是对数据库操作的利器了。这篇文章主要介绍了Mybatis分页插件PageHelper的配置和使用方法,需要的朋友可以参考下2017-12-12
Spring Data JPA在eladmin中的深度应用实践
本文详细介绍了eladmin系统中SpringDataJPA的应用实践,涵盖了实体关系建模、复杂查询构建、性能优化策略等方面,为开发者提供了JPA使用的最佳实践,感兴趣的朋友跟随小编一起看看吧2026-01-01


最新评论