Java 中浅拷贝和深拷贝的区别

10 Sept 2024 | 5 分钟阅读

克隆是 Java 中一个基本概念,它允许开发人员创建对象的副本。此过程对于各种场景至关重要,例如保存对象的状态、创建备份或实现某些设计模式。然而,Java 提供了两种不同的克隆机制:浅拷贝深拷贝。理解这两种方法之间的区别对于有效的对象复制和避免代码中意外行为至关重要。

浅克隆

浅拷贝创建一个新对象,但不会复制原始对象引用的对象。相反,它会复制对这些对象的引用。因此,原始对象和克隆对象都共享对同一对象的引用。浅拷贝是一个快速有效的过程,但它有一些限制,尤其是在处理复杂对象结构时。

让我们看一个例子

在此示例中,如果我们对 Person 对象执行浅拷贝,name 属性将被复制,但原始 Person 对象和克隆的 Person 对象都将引用同一个 Address 对象。通过一个实例对 Address 对象所做的更改将在另一个实例中反映出来。

深克隆

另一方面,深拷贝会创建一个原始对象的完全独立的副本,以及其引用的所有对象的副本。这确保对克隆对象或其嵌套对象所做的更改不会影响原始对象,反之亦然。然而,深拷贝可能更耗费资源,并且可能涉及复杂的对象遍历。

为了实现深拷贝,一种常用的方法是序列化和反序列化对象,从而有效地创建一个没有任何共享引用的新副本。以下是使用 ObjectOutputStream 和 ObjectInputStream 类的示例:

需要注意的是,要使对象能够进行深拷贝,其所有嵌套对象也必须支持克隆或序列化。

选择浅拷贝还是深拷贝

选择浅拷贝还是深拷贝取决于我们应用程序的具体要求。浅拷贝速度更快、更简单,适用于共享引用可接受的情况。但是,如果您需要对象的嵌套对象的真正独立副本,深拷贝是更安全的选择,尽管性能成本可能更高。

文件名: CloningExample.java

输出

Original Person: Person{name='UpdatedJohn', address=Address{city='UpdatedCityA', country='CountryA'}}
Shallow Cloned Person: Person{name='John', address=Address{city='UpdatedCityA', country='CountryA'}}
Deep Cloned Person: Person{name='John', address=Address{city='CityA', country='CountryA'}}

注意:输出可能会根据您使用的 Java 版本而有所不同。关键点是要观察原始对象的变化如何影响克隆对象。

解释

在此示例中,我们有一个包含 name 和 Address 对象的 Person 类。Address 类已实现可序列化以进行深拷贝。CloningExample 类演示了原始 Person 对象的创建以及浅拷贝和深拷贝的生成。输出将展示浅拷贝和深拷贝之间的差异。

原始 Person

原始 Person 对象已更新为新的姓名(“UpdatedJohn”)和 Address 对象中的更新城市(“UpdatedCityA”)。

浅拷贝的 Person

浅拷贝(shallowClonePerson)反映了对原始 Person 对象所做的更改。原始对象和浅拷贝都共享对同一 Address 对象的引用,因此对 Address 对象的更改在这两者中可见。

深拷贝的 Person

深拷贝(deepClonePerson)不受对原始 Person 对象所做的更改的影响。它拥有通过深拷贝过程创建的 Address 对象的自己的副本。因此,对原始对象或浅拷贝的更改不会影响深拷贝。

此输出突显了浅拷贝和深拷贝之间的差异,说明了在这两种情况下嵌套对象的更改是如何管理的。

Java 中的浅拷贝与深拷贝

方面浅克隆深克隆
复制方法创建一个新对象,但共享对嵌套对象的引用。创建完全独立的副本以及嵌套对象的副本。
性能通常更快、更有效。可能较慢,尤其是对于复杂的对象结构。
对象引用原始对象和克隆对象共享对同一嵌套对象的引用。原始对象和克隆对象对嵌套对象拥有独立的副本。
对原始对象所做的更改对于共享引用,更改会影响克隆对象,反之亦然。由于独立的副本,一个对象中的更改不会影响另一个对象。
实施利用 clone() 方法或手动复制对象字段。通常涉及序列化和反序列化以创建深拷贝。
嵌套对象要求对嵌套对象没有特定要求。所有嵌套对象都必须支持克隆或序列化才能进行深拷贝。

该表总结了浅拷贝和深拷贝之间的主要区别,为理解它们在 Java 中的特性和用例提供了快速参考。

总之,理解 Java 中浅拷贝和深拷贝的区别对于编写健壮且可维护的代码至关重要。我们选择浅拷贝的效率还是深拷贝的隔离性,取决于我们应用程序的具体需求以及我们正在处理的对象本身的性质。