Java中的clone,sallow copy和deep copy的区别解析

 更新时间:2026年02月27日 09:10:29   作者:杨DaB  
本文给大家介绍了Java中的clone,sallow copy和deep copy的区别解析,介绍了克隆的概念和作用,以及浅克隆和深克隆的区别,感兴趣的朋友跟随小编一起看看吧

克隆的概念和作用

  1. 方法需要 return 引用类型,但又不希望自己本身 持有引用类型的对象被修改。
  2. 程序之间方法的调用时参数的传递。有些场景为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数传递。

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

  1. 实现 Cloneable 接口,重写 clone() 方法。
  2. 不实现 Cloneable 接口,会报 CloneNotSupportedException 异常。
  3. Objectclone() 方法是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。

方法1、对象的属性的Class 也实现 Cloneable 接口,克隆对象的同时也克隆属性。

@Override
public Object clone() throws CloneNotSupportedException {
    DPerson p = (DPerson)super.clone();
    p.setFood((DFood)p.getFood().clone());
    return p;
}

如下:

  1. DPerson 类实现 Cloneable接口,重写clone()方法,将对象 和属性全部clone;
  2. DPerson 引用的对象Food类 实现Cloneable接口,重写clone()方法;
  3. 在主程序中调用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;
    }

如下:

  1. 整体思想:将数据序列化成为字节流,再反序列化为数据行程对象的clone,做出一个全新的、与原始对象完全独立的对象。;
  2. 编写cloneBySerializable()方法,创建一个Object对象,设置为成员变量初始化为null,做传输使用;
  3. 创建 ByteArrayOutputStream 容器,用于接收和存储后续写入的字节数据
  4. 创建一个对象输出流 ObjectOutputStream oos = new ObjectOutputStream(baos); ,包装在 ByteArrayOutputStream 之上,不能独立存在,必须依赖一个底层的输出流,指定数据最终的去向。
  5. 使用writeObject 方法,将当前对象 (this) 序列化为字节流。这些字节数据被写入到 ByteArrayOutputStream这个内存容器中。
  6. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 将对象反序列化会bais中,在通过 ObjectInputStream 进行读取, ObjectInputStream 必须包装在 ByteArrayInputStream 之中;
  7. 通过readObject 方法,从字节流中读取数据并反序列化,最终创建出一个全新的对象。
  8. 在主程序中调用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 实现自动关闭资源的方法

    这篇文章主要介绍了Java使用 try-with-resources 实现自动关闭资源的方法,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • java对list<Object>进行手动分页实现

    java对list<Object>进行手动分页实现

    本文主要介绍了java对list<Object>进行手动分页实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Mybatis分页插件PageHelper的配置和简单使用方法(推荐)

    Mybatis分页插件PageHelper的配置和简单使用方法(推荐)

    在使用Java Spring开发的时候,Mybatis算是对数据库操作的利器了。这篇文章主要介绍了Mybatis分页插件PageHelper的配置和使用方法,需要的朋友可以参考下
    2017-12-12
  • java实现简单斗地主(看牌排序)

    java实现简单斗地主(看牌排序)

    这篇文章主要介绍了java实现简单斗地主,看牌进行排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2010-11-11
  • 关于maven下载慢的问题

    关于maven下载慢的问题

    这篇文章主要介绍了关于maven下载慢的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • SpringBoot中服务消费的实现

    SpringBoot中服务消费的实现

    本文主要介绍了SpringBoot中服务消费的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Spring Data JPA在eladmin中的深度应用实践

    Spring Data JPA在eladmin中的深度应用实践

    本文详细介绍了eladmin系统中SpringDataJPA的应用实践,涵盖了实体关系建模、复杂查询构建、性能优化策略等方面,为开发者提供了JPA使用的最佳实践,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • Java并发编程之CountDownLatch源码解析

    Java并发编程之CountDownLatch源码解析

    这篇文章主要介绍了Java并发编程之CountDownLatch源码解析,文中有非常详细的代码示例,对正在学习java并发编程的小伙伴们有很好的帮助,需要的朋友可以参考下
    2021-04-04
  • 使用Java实现在PDF插入页眉页脚

    使用Java实现在PDF插入页眉页脚

    在处理PDF文档时,有时需要为文档中的每一页添加页眉和页脚,这篇文章主要为大家详细介绍了如何使用Java为PDF文件添加页眉、页脚,感兴趣的可以了解下
    2024-03-03
  • Java使用Collections.sort()排序的方法

    Java使用Collections.sort()排序的方法

    这篇文章介绍了Java使用Collections.sort()排序的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12

最新评论