一文搞懂Java克隆及深拷贝与浅拷贝的区别

 更新时间:2023年08月02日 11:52:44   作者:蜀山剑客李沐白  
在编程中,通常通过实现Cloneable接口和重写clone方法来实现对象的克隆,然而,需要注意的是克隆操作可能存在深拷贝和浅拷贝的区别,在使用时需要根据实际需求选择合适的克隆方式,本文就给大家详细讲讲什么是克隆以及深拷贝与浅拷贝的区别,需要的朋友可以参考下

什么是克隆,为什么在编程中使用克隆

克隆是指创建一个对象的副本,使得新创建的对象在内容上与原始对象相同。在编程中,克隆是常用的技术之一,它具有以下几个重要用途和优势:

  • 复制对象:使用克隆可以创建一个与原始对象相同的新对象,包括对象的属性和状态。这样可以在不影响原始对象的情况下,对新对象进行修改、操作、传递等。这在某些场景下非常有用,可以避免重新创建和初始化一个对象。

  • 隔离性与保护:通过克隆,可以创建一个独立于原始对象的副本。这样,修改克隆对象时,不会影响到原始对象,从而实现了对象之间的隔离性。这对于多线程环境下的并发操作或者保护重要数据具有重要意义。

  • 性能优化:有时候,通过克隆对象可以提高程序的性能。在某些场景下,对象的创建和初始化过程可能较为耗时,如果需要多次使用这个对象,通过克隆原始对象可以避免重复的创建和初始化过程,从而提高程序的执行效率。

  • 原型模式:克隆在设计模式中有一个重要的角色,即原型模式。原型模式通过克隆来创建对象的实例,而不是使用传统的构造函数。这样可以提供更灵活的对象创建方式,并且避免了频繁的子类化。

在编程中,通常通过实现Cloneable接口和重写clone方法来实现对象的克隆。然而,需要注意的是克隆操作可能存在深拷贝和浅拷贝的区别,在使用时需要根据实际需求选择合适的克隆方式。

什么是深拷贝和浅拷贝

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在克隆(Clone)操作中经常遇到的两个概念,它们描述了克隆操作对于对象内部引用的处理方式。

  • 浅拷贝(Shallow Copy):

    • 浅拷贝指在克隆操作中,只复制对象本身以及对象内部的基本数据类型的属性,而不复制对象内部的引用类型的属性。
    • 浅拷贝仅仅创建了一个新对象,该对象与原始对象共享对同一引用类型属性的访问。如果原始对象的引用类型属性被修改,浅拷贝的对象也会受到影响。
    • 在浅拷贝中,新对象和原始对象指向同一块内存区域,因此对其中一个对象进行修改可能会影响到另一个对象。
  • 深拷贝(Deep Copy):

    • 深拷贝指在克隆操作中,除了复制对象本身以及对象内部的基本数据类型的属性外,还要递归地复制对象内部的引用类型的属性。即深度克隆了所有引用类型的属性。
    • 深拷贝创建了一个完全独立的新对象,该对象与原始对象没有任何关联,对新对象和原始对象的修改互不影响。
    • 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。

为了实现深拷贝,需要对对象内部的引用类型属性进行递归复制。常见的实现深拷贝的方式包括:

  • 通过序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象,这样可以创建一个与原始对象完全独立的副本。
  • 通过逐个复制引用类型属性:对于每个引用类型的属性,创建一个新的实例并将原始对象属性的内容复制到新的实例中。

需要注意的是,并非所有对象都能进行深拷贝。某些对象或者类中的属性可能是不可变的,无需拷贝;某些对象可能包含循环引用,无法完全复制。因此,在进行克隆操作时,需要根据具体情况选择合适的拷贝方式。

深拷贝和浅拷贝的主要区别在于对于对象内部引用类型属性的处理方式。

  • 数据复制层次的深度:

    • 浅拷贝只复制对象本身以及对象内部的基本数据类型的属性,不会递归地复制引用类型的属性。因此,在浅拷贝中,新对象和原始对象共享对同一引用类型属性的访问。
    • 深拷贝除了复制对象本身和基本数据类型的属性外,还会递归地复制对象内部的引用类型的属性。这样,深拷贝创建了一个完全独立的新对象,与原始对象没有任何关联。
  • 对象之间的关联性:

    • 浅拷贝得到的新对象与原始对象共享对同一引用类型属性的访问。如果对其中一个对象的引用类型属性进行修改,另一个对象也会受到影响。
    • 深拷贝得到的新对象与原始对象没有任何关联,修改其中一个对象的引用类型属性不会影响到另一个对象。
  • 内存区域的分配:

    • 在浅拷贝中,新对象和原始对象指向同一块内存区域。因此,对其中一个对象进行修改可能会影响到另一个对象。
    • 在深拷贝中,新对象和原始对象分别对应不同的内存区域,它们之间不存在引用关系,因此修改其中一个对象不会影响到另一个对象。

浅拷贝示例

实现 Cloneable 接口和重写 clone() 方法:

  • Java 中的 Cloneable 接口是一个标记接口,没有定义任何方法。通过实现 Cloneable 接口并重写 clone() 方法,可以实现对象的浅拷贝。
  • 在 clone() 方法中,调用父类的 clone() 方法,并将其返回值进行类型转换即可完成浅拷贝。

下面是一个示例代码,演示了如何使用 Cloneable 接口和 clone() 方法实现浅拷贝:

class Person implements Cloneable {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 25);
        try {
            // 浅拷贝
            Person person2 = (Person) person1.clone();
            System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25
            System.out.println(person2.getName() + " " + person2.getAge()); // Alice 25
            person2.setName("Bob");
            person2.setAge(30);
            System.out.println(person1.getName() + " " + person1.getAge()); // Alice 25
            System.out.println(person2.getName() + " " + person2.getAge()); // Bob 30
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在上述示例中,我们创建了一个 Person 类,并实现了 Cloneable 接口。在 clone() 方法中直接调用了父类的 clone() 方法,并进行了类型转换。通过调用 clone() 方法,可以得到一个新的对象 person2,它与原始对象 person1 具有相同的属性值。当修改 person2 的属性时,不会影响到 person1

深拷贝示例

使用序列化和反序列化:

  • 将对象写入到字节流中,然后再从字节流中读取出来,这个过程会重新创建一个完全独立的对象,实现了深拷贝。
  • 为了实现深拷贝,需要将对象及其关联的对象都实现序列化。

下面是一个示例代码,演示了如何使用序列化和反序列化实现深拷贝:

import java.io.*;
class Address implements Serializable {
    private String city;
    private String street;
    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    public void setCity(String city) {
        this.city = city;
    }
    public void setStreet(String street) {
        this.street = street;
    }
    public String getCity() {
        return city;
    }
    public String getStreet() {
        return street;
    }
}
class Person implements Serializable {
    private String name;
    private int age;
    private Address address;
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setAddress(Address address) {
        this.address = address;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public Address getAddress() {
        return address;
    }
}
public class Main {
    public static void main(String[] args) {
        Address address = new Address("City", "Street");
        Person person1 = new Person("Alice", 25, address);
        // 深拷贝
        Person person2 = deepCopy(person1);
        System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City
        System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Alice 25 City
        person2.setName("Bob");
        person2.setAge(30);
        person2.getAddress().setCity("New City");
        System.out.println(person1.getName() + " " + person1.getAge() + " " + person1.getAddress().getCity()); // Alice 25 City
        System.out.println(person2.getName() + " " + person2.getAge() + " " + person2.getAddress().getCity()); // Bob 30 New City
    }
    public static <T extends Serializable> T deepCopy(T object) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            return (T) objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

在上述示例中,我们创建了一个 Address 类和一个 Person 类,它们都实现了 Serializable 接口。通过序列化和反序列化操作,我们可以实现深拷贝。在 deepCopy() 方法中,我们使用字节流将对象写入到内存中,并从内存中读取出来,从而得到一个新的独立对象。通过调用 deepCopy() 方法,可以得到一个新的对象 person2,它与原始对象 person1 完全独立。在修改 person2 的属性时,不会影响到 person1。 值得注意的是,要实现深拷贝,所有相关的类都需要实现 Serializable 接口。

深拷贝和浅拷贝的区别

深拷贝(Deep Copy):

  • 适用场景:

    • 当源对象包含引用类型的属性时,如果需要复制对象及其子对象的所有属性,而不仅仅只是复制引用,就需要使用深拷贝。
    • 当希望修改副本对象的属性不影响原始对象时,需要使用深拷贝。
  • 工作原理:

    • 深拷贝将源对象及其关联的全部对象进行递归复制,每个对象都拥有独立的内存空间,修改副本对象不会影响原始对象。
  • 实现方式:

    • 使用递归或者拷贝构造函数来复制对象及其子对象的属性。
  • 示例场景:

    • 复制复杂对象的副本,使其成为独立的个体,例如:拷贝一个包含集合、嵌套对象等的数据结构。
    • 对象图的克隆,当原对象包含子对象,并且对子对象的修改不应该影响原对象时。

浅拷贝(Shallow Copy):

  • 适用场景:

    • 当源对象的属性全为基本数据类型或者不可变对象,并且不需要复制引用类型的属性时,可以使用浅拷贝。
    • 当希望修改副本对象的属性同时影响原始对象时,可以使用浅拷贝。
  • 工作原理:

    • 浅拷贝只复制对象及其引用,而不复制引用指向的实际对象,新旧对象将共享同一个引用对象。修改副本对象会影响原始对象。
  • 实现方式:

    • 通常使用对象的 clone() 方法来进行浅拷贝。
  • 示例场景:

    • 快速创建对象副本,以便在某些操作中对其进行修改,同时保留原始对象。
    • 在某些情况下,共享一部分数据以节省内存和提高性能。

以上就是一文搞懂Java克隆技术及深拷贝与浅拷贝的区别的详细内容,更多关于Java克隆技术及深拷贝与浅拷贝的资料请关注脚本之家其它相关文章!

相关文章

  • springboot如何开启一个监听线程执行任务

    springboot如何开启一个监听线程执行任务

    这篇文章主要介绍了springboot如何开启一个监听线程执行任务问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • java实现字符串和日期类型相互转换的方法

    java实现字符串和日期类型相互转换的方法

    这篇文章主要介绍了java实现字符串和日期类型相互转换的方法,涉及java针对日期与字符串的转换与运算相关操作技巧,需要的朋友可以参考下
    2017-02-02
  • Java中的volatile关键字原理深入解析

    Java中的volatile关键字原理深入解析

    这篇文章主要介绍了Java中的volatile关键字原理深入解析,volatile是Java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量,需要的朋友可以参考下
    2023-12-12
  • Spring Boot配置读取实现方法解析

    Spring Boot配置读取实现方法解析

    这篇文章主要介绍了Spring Boot配置读取实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • java 读写文件[多种方法]

    java 读写文件[多种方法]

    前两天用到读写文件的操作,上网搜了一些这方面的资料。很有用的。
    2008-11-11
  • java实现简单网络象棋游戏

    java实现简单网络象棋游戏

    这篇文章主要为大家详细介绍了java实现简单网络象棋游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • SpringBoot实现动态多线程并发定时任务

    SpringBoot实现动态多线程并发定时任务

    这篇文章主要为大家详细介绍了SpringBoot实现动态多线程并发定时任务,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-05-05
  • 业务系统的Prometheus实践示例详解

    业务系统的Prometheus实践示例详解

    这篇文章主要为大家介绍了业务系统的Prometheus实践示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • 基于Java实现简单的时序数据压缩算法

    基于Java实现简单的时序数据压缩算法

    这篇文章主要为大家详细介绍了如何利用Java语言实现简单易懂的时序数据压缩算法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-06-06
  • Java并发之AQS与自旋锁详解

    Java并发之AQS与自旋锁详解

    这篇文章主要介绍了Java并发之AQS与自旋锁详解,类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch,需要的朋友可以参考下
    2023-10-10

最新评论