您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

深拷贝和浅拷贝:如何选择最适合你的对象复制技术?

时间:2023-05-06 11:53:31  来源:  作者:



JAVA中的对象复制主要有三种方式:clone、深拷贝和浅拷贝。这些技术对于Java开发人员来说非常重要,因为它们可以帮助开发人员管理复杂的数据结构。本文将详细讨论这三种技术,包括其工作方式,优缺点以及使用时需要避免的陷阱。

 

1. Java对象clone

Java对象的clone是一种创建对象副本的简单方法,它可以避免重新实例化对象并复制现有对象的字段。当您需要创建一个与现有对象具有相同状态的新对象时,这种方法非常有用。

1.1 clone() 方法

在Java中,Object类提供了一个clone()方法,该方法会返回当前对象的一个副本。由于clone()方法是从Object类继承而来的,所以它可以被任何Java对象调用。Java中的clone()方法是一个浅拷贝,它只复制引用类型的地址,不会复制地址指向的对象。

如果您想使用clone()方法,您的类必须实现Cloneable接口,该接口标记对象“可克隆”。否则,您将会抛出
CloneNotSupportedException异常。

下面是一个示例:

public class Person implements Cloneable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

在上面的示例中,Person类实现了Cloneable接口,并覆盖了Object类的clone()方法。现在,我们可以使用该方法复制一个Person对象。

1.2 浅拷贝

在Java中,clone()方法是浅拷贝。这意味着它仅复制基本数据类型和对象引用的值。如果对象引用指向的是同一个对象,则副本和原始对象都将引用该对象的地址。

下面是一个示例:

public class Person implements Cloneable {
    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;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Address {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
}

public class MAIn {
    public static void main(String[] args) {
        Address address = new Address("123 Main St", "Anytown");
        Person person1 = new Person("John Doe", 42, address);

        try {
            // Clone the person
            Person person2 = (Person) person1.clone();

            // Modify the original object's field
            person1.getAddress().setCity("New York");

            // Print out the fields for both objects
            System.out.println(person1.getName() + ": " + person1.getAddress().getCity());
            System.out.println(person2.getName() + ": " + person2.getAddress().getCity());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们创建了两个Person对象,并且将一个Address对象传递给他们。然后,我们克隆了第一个Person对象并将其存储在另一个Person对象中。接下来,我们修改原始对象的address字段,并打印出两个对象的地址以及城市字段。

由于clone()方法是浅拷贝,所以person1和person2都引用同一个Address对象。这意味着当我们修改其中一个对象的Address对象时,另一个对象也会收到影响。

1.3 深拷贝

深拷贝是一种复制对象及其所有子对象的技术。与浅拷贝不同,深拷贝会复制对象的所有字段和子对象,而不是只复制引用类型的地址。这意味着在深拷贝期间创建的副本与原始对象没有任何关联。

 

有几种方法可以实现深拷贝。其中一种方法是通过序列化和反序列化来完成。另一种方法是使用递归方式遍历整个对象图,并复制每个对象及其子对象。

下面是一个示例:

import java.io.*;

public 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 Person clone() throws IOException, ClassNotFoundException {
        // Serialize the object
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);

        // Deserialize the object
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (Person) ois.readObject();
    }
}

public class Address implements Serializable {
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
}

public class Main {
    public static void main(String[] args) {
        Address address = new Address("123 Main St", "Anytown");
        Person person1 = new Person("John Doe", 42, address);

        try {
            // Clone the person
            Person person2 = person1.clone();

            // Modify the original object's field
            person1.getAddress().setCity("New York");

            // Print out the fields for both objects
            System.out.println(person1.getName() + ": " + person1.getAddress().getCity());
            System.out.println(person2.getName() + ": " + person2.getAddress().getCity());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们实现了一个深拷贝方法,并使用序列化和反序列化来完成。我们创建了两个Person对象,并将一个Address对象传递给他们。然后,我们克隆了第一个Person对象并将其存储在另一个Person对象中。接下来,我们修改原始对象的address字段,并打印出两个对象的地址以及城市字段。

由于我们使用了深拷贝技术,person1和person2引用的是不同的Address对象。这意味着当我们修改其中一个对象的Address对象时,另一个对象不会收到影响。

2. 浅拷贝 vs 深拷贝

浅拷贝和深拷贝都有其优点和缺点。下面是一些重要的区别:

2.1 复制效率

相对于深拷贝,浅拷贝效率更高。这是因为在浅拷贝中只复制基本数据类型和对象引用的值。与此相比,在深拷贝中需要递归地复制整个对象图,这可能会导致性能问题。

2.2 内存使用

由于深拷贝复制了整个对象图,所以其需要更多的内存。与此相比,在浅拷贝中只需要复制基本数据类型和对象引用的值,因此它需要更少的内存。

2.3 对象关系

在浅拷贝中,副本和原始对象共享所有的子对象。这意味着当我们修改其中一个对象的子对象时,另一个对象也会收到影响。

与此相反,在深拷贝中,副本和原始对象不共享任何子对象。这意味着当我们修改其中一个对象的子对象时,另一个对象不会受到影响。

3. 避免clone()方法的陷阱

虽然clone()方法是一种方便的创建对象副本的方法,但它也有一些陷阱需要注意。下面是一些重要的点:

 

3.1 clone()方法不会调用构造函数

当我们使用clone()方法创建一个对象副本时,它不会调用构造函数。这意味着我们无法保证副本与原始对象具有相同的状态。

例如,如果我们在构造函数中初始化了某个字段,并且该字段在后来被修改了,那么克隆的对象可能具有不同的字段值。

3.2 clone()方法只能复制实现Cloneable接口的对象

如果我们要使用clone()方法创建对象副本,那么我们必须确保该对象实现了Cloneable接口。如果没有实现,则会抛出
CloneNotSupportedException异常。

此外,在实现Cloneable接口时,我们还需要覆盖Object类的clone()方法。如果忘记覆盖该方法,则将获得默认的浅拷贝行为。

3.3 clone()方法是一个受保护的方法

由于clone()方法是一个受保护的方法,因此它不能从外部访问。这意味着我们必须在子类中覆盖该方法才能使用它。

3.4 clone()方法可能导致性能问题

由于clone()方法是浅拷贝,因此它可能会引起性能问题。如果对象图很大,则递归地复制整个对象图可能会非常耗时。

3.5 clone()方法与不可变对象

由于clone()方法返回的是一个副本,它可能会破坏不可变对象的不变性。如果我们要在不可变对象上使用clone()方法,则需要确保复制的对象也是不可变的。否则,我们不能保证它们始终具有相同的状态。

4. 进阶技巧

下面是一些高级技巧,可以帮助您更好地使用clone()方法和深拷贝:

4.1 使用序列化实现深拷贝

如前所述,我们可以通过序列化和反序列化来实现深拷贝。这是因为序列化和反序列化过程中,整个对象图都被复制了。此外,Java也提供了很多方便的库和工具来支持序列化操作。

4.2 实现自定义clone()方法

由于clone()方法是受保护的,因此我们无法从外部直接调用它。如果我们想要使用clone()方法创建对象副本,我们需要在子类中覆盖该方法。

此外,在覆盖clone()方法时,我们可以选择实现自定义逻辑,以确保新副本的状态正确。

4.3 使用第三方库

除了Java内置的clone()方法和序列化机制外,还有许多第三方库可以帮助我们实现深拷贝和浅拷贝。例如,Apache Commons库提供了BeanUtils和SerializationUtils等工具类,可以方便地进行对象复制。

5. 总结

Java中的clone()方法、浅拷贝和深拷贝都是非常有用的技术。它们可以帮助开发人员管理复杂的数据结构,并避免重复创建对象。

然而,这些技术也存在一些陷阱需要注意。如果我们没有正确地使用它们,就可能会导致状态不一致、性能问题或其他异常。

最后,我们还介绍了一些进阶技巧,可以帮助您更好地使用clone()方法和深拷贝。如果您能够正确地使用它们,那么它们将成为您在Java开发中的有力工具。



Tags:对象复制   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
深拷贝和浅拷贝:如何选择最适合你的对象复制技术?
Java中的对象复制主要有三种方式:clone、深拷贝和浅拷贝。这些技术对于Java开发人员来说非常重要,因为它们可以帮助开发人员管理复杂的数据结构。本文将详细讨论这三种技术,...【详细内容】
2023-05-06  Search: 对象复制  点击:(480)  评论:(0)  加入收藏
七种对象复制工具类,你最看好哪个?
作 者: 鸭血粉丝原文链接:https://mp.weixin.qq.com/s/RKeUEztGR-nAc_XemF2pOw日常编程中,我们会经常会碰到对象属性复制的场景,就比如下面这样一个常见的三层 MVC 架构。 当我...【详细内容】
2020-08-25  Search: 对象复制  点击:(551)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(14)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(24)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(25)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(57)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(68)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(72)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(89)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(106)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(97)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(75)  评论:(0)  加入收藏
站内最新
站内热门
站内头条