在 Java 中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象拷贝的两种方式,主要区别在于它们如何处理对象的内部引用。
目录
一、浅拷贝(Shallow Copy)
实现方式
二、深拷贝(Deep Copy)
实现方式
1、手动深拷贝
2、通过序列化实现深拷贝
深拷贝中的注意事项
深拷贝的应用场景
总结
一、浅拷贝(Shallow Copy)
浅拷贝是指仅拷贝对象的基本类型字段和引用类型字段的引用,而不是引用类型所指向的对象本身。因此,浅拷贝后的对象与原对象的引用类型字段共享相同的对象。如果原对象或拷贝对象中的引用类型被修改,两个对象都会受到影响。
实现方式
通过 clone()
方法实现浅拷贝。需要类实现 Cloneable
接口,并重写 clone()
方法。
class Person implements Cloneable {
String name;
int age;
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(); // 浅拷贝
}
}
class Address {
String city;
public Address(String city) {this.city = city;}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("John", 25, address);
Person person2 = (Person) person1.clone(); // 浅拷贝
System.out.println(person1.address.city); // 输出 "New York"
System.out.println(person2.address.city); // 输出 "New York"
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 输出 "Los Angeles",由于是浅拷贝,两者共享相同的地址引用
}
}
在这个例子中,person1
和 person2
共享同一个 Address
对象,修改 person2
的 address
会影响 person1
。
二、深拷贝(Deep Copy)
深拷贝不仅拷贝对象的基本类型字段,还会递归地拷贝对象中所有的引用对象。因此,拷贝后的对象与原对象完全独立,互不影响。修改一个对象的引用类型字段不会影响另一个对象。
深拷贝之所以能够实现对象的完全独立,关键在于它对对象中的引用类型字段进行了逐层递归的复制。其基本原理如下:
-
基本数据类型的复制:对于 Java 中的基本数据类型(如
int
、double
、char
等),深拷贝与浅拷贝是一样的,即直接拷贝数值。这是因为基本类型本质上是存储在栈中的固定长度的值,不涉及引用或指针。 -
引用类型的处理:深拷贝时,引用类型(如对象、数组等)不会直接拷贝引用,而是会创建一个全新的副本,并且这个副本与原引用指向的对象互不相关。这种“递归”拷贝意味着如果对象内部还有对象,它们也会被逐一深拷贝。
实现方式
- 手动实现:手动编写深拷贝逻辑,通过递归地调用
clone()
方法来拷贝所有引用对象。 - 通过序列化实现:将对象序列化为字节流,再反序列化为新对象。
特点:
- 基本类型字段会被完全复制。
- 引用类型字段也会被完全复制,创建新的对象。
- 深拷贝相比浅拷贝更耗时,因为需要递归复制所有引用类型。
1、手动深拷贝
手动实现深拷贝时,程序员需要遍历每个引用类型的字段,并递归调用 clone()
或其他方式来复制子对象。每个引用类型字段的深拷贝都需要单独处理。
以一个简单的对象层次结构为例:
class Person implements Cloneable {
String name;
int age;
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 {
Person cloned = (Person) super.clone();
cloned.address = (Address) this.address.clone(); // 深拷贝 address
return cloned;
}
}
class Address implements Cloneable {
String city;
public Address(String city) {
this.city = city;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("John", 25, address);
Person person2 = (Person) person1.clone(); // 深拷贝
System.out.println(person1.address.city); // 输出 "New York"
System.out.println(person2.address.city); // 输出 "New York"
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 仍输出 "New York",因为进行了深拷贝,两个对象完全独立
}
}
在这个例子中,Person
类中引用了 Address
对象。为了确保深拷贝,我们在 Person
类的 clone()
方法中显式地对 address
字段调用 clone()
方法,从而实现 Address
的深拷贝。这样 Person
对象和 Address
对象都是独立的。
2、通过序列化实现深拷贝
如果对象和所有子对象实现了 Serializable
接口,可以使用序列化来实现深拷贝。
序列化是深拷贝的一种简便方式。通过将对象序列化为字节流,然后再将字节流反序列化为新的对象,可以实现深拷贝。这种方式适用于复杂的对象图,甚至对象之间有循环引用时也能正确处理。
通过序列化实现深拷贝的关键步骤如下:
- 序列化:将对象及其所有引用对象通过字节流写入到一个存储介质中(例如
ByteArrayOutputStream
)。 - 反序列化:从存储介质中读取字节流,并重新构建对象及其引用对象,从而生成与原始对象完全独立的新对象。
import java.io.*;
class Person implements Serializable {
String name;
int age;
Address address;
public Person(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public Person deepCopy() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(this);
// 反序列化
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
return (Person) in.readObject();
}
}
class Address implements Serializable {
String city;
public Address(String city) {
this.city = city;
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("New York");
Person person1 = new Person("John", 25, address);
Person person2 = person1.deepCopy(); // 深拷贝
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 输出 "New York",两个对象独立
}
}
在这个例子中,Person
对象和其引用的 Address
对象都实现了 Serializable
接口。通过序列化和反序列化,生成了完全独立的 Person
和 Address
对象。
深拷贝中的注意事项
(1)对象之间的引用关系
深拷贝必须递归地处理对象中的所有引用类型,因此可能会遇到复杂的对象图结构。如果对象之间存在循环引用,序列化方案可以很好地处理这种情况,因为序列化机制会自动检测和处理对象循环引用的问题。
(2)性能成本
深拷贝的性能开销通常比浅拷贝大,尤其是当对象结构复杂时,递归地创建新的对象会消耗更多的时间和内存。
序列化虽然实现较为简便,但由于涉及字节流的创建和解析,性能相对较低。
(3)不可变对象:
对于不可变对象(如 String
、Integer
等),深拷贝和浅拷贝都无需特别处理,因为不可变对象在拷贝中不会受到修改的影响。
深拷贝的应用场景
(1)避免副作用:在需要确保对象之间完全独立,防止一个对象的修改影响另一个对象时,深拷贝是必需的。例如,当在多线程环境中使用对象时,使用深拷贝可以避免共享对象带来的数据不一致问题。
(2)复杂对象结构:当对象中嵌套了其他对象,并且这些对象可能会被修改时(例如树结构、图结构),深拷贝确保了各个层次的对象都能保持独立。
总结
- 浅拷贝:拷贝对象的基本类型字段和引用类型的引用,两个对象共享相同的引用对象。
- 深拷贝:完全独立的对象拷贝,包括对象的引用类型字段的递归拷贝。