浅拷贝和深拷贝(Java 与 JavaScript)
一、Java 浅拷贝和深拷贝
在Java中,浅拷贝和深拷贝的主要区别在于对对象的引用和内容的复制方式。
浅拷贝
Java 的类型有基本数据类型和引用类型,基本数据类型是可以由 CPU 直接操作的类型,无论是深拷贝还是浅拷贝,都是会复制出另一份。而引用类型仅仅是一个指针,指向的是这个对象在堆内存中分配的内存。
举例:
class Address {
String city;
Address(String city) {
this.city = city;
}
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class ShallowCopyExample {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("Alice", address);
Person person2 = (Person) person1.clone();
// 修改 person2 的地址
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 输出: Los Angeles
}
}
浅拷贝:仅仅是将这个指针拷贝了一份出来,只复制对象的引用,两个指针都指向相同的堆内存地址,原始对象和拷贝对象共享相同的内部对象;
深拷贝
举例:
class Address {
String city;
Address(String city) {
this.city = city;
}
// 深拷贝方法
public Address deepCopy() {
return new Address(this.city);
}
}
class Person implements Cloneable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = this.address.deepCopy(); // 深拷贝地址
return cloned;
}
}
public class DeepCopyExample {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("Alice", address);
Person person2 = (Person) person1.clone();
// 修改 person2 的地址
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 输出: New York
}
}
深拷贝:拷贝的就不仅仅是一个指针,还会在堆内存中将原来的对象也拷贝出来一份,原始对象和拷贝对象之间完全独立。
需要注意的一点是:多线程中使用浅拷贝时,多个线程可能会同时修改共享的内部对象,导致数据不一致。这种情况需要通过同步机制(如 synchronized
)来避免数据冲突。
当然,深拷贝还常常使用序列化的方式来实现:
import java.io.*;
// 可序列化的类
class Address implements Serializable {
String city;
Address(String city) {
this.city = city;
}
}
class Person implements Serializable {
String name;
Address address;
Person(String name, Address address) {
this.name = name;
this.address = address;
}
// 深拷贝方法
public Person deepCopy() throws IOException, ClassNotFoundException {
// 写入到字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 从字节流中读取出对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
}
}
public class SerializationDeepCopyExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Address address = new Address("New York");
Person person1 = new Person("Alice", address);
Person person2 = person1.deepCopy();
// 修改 person2 的地址
person2.address.city = "Los Angeles";
System.out.println(person1.address.city); // 输出: New York
}
}
或者单独抽象出一个方法出来实现:
/**
* 序列化实现深拷贝:
* 这个方法首先将original对象序列化到一个ByteArrayOutputStream中,然后立即使用ByteArrayInputStream从这个字节流中反序列化对象,
* 从而得到一个完全独立的深拷贝。这种方法的优点是它相对简单,且能自动处理对象图中的所有复杂关系。然而,它可能比直接使用拷贝构造函数或者克隆方法更慢,
* 且只有实现了Serializable接口的对象才能被复制。
*/
public class DeepCopyViaSerialization {
// 实现深拷贝的方法
public static <T> T deepCopy ( T original ) {
T copied = null;
try {
// 创建一个字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(original);
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
copied = (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return copied;
}
}
二、JavaScript 浅拷贝和深拷贝
在JavaScript中,浅拷贝和深拷贝是两种复制对象的方式。
浅拷贝
浅拷贝只复制对象的第一层属性,若属性是引用类型(如对象、数组),则复制的是引用。常用方法包括:
-
Object.assign()
// Object.assign 缺点: 仅能实现浅拷贝,不适用于深层嵌套对象 const obj1 = { a: 1, b: { c: 2 } }; const shallowCopy = Object.assign({}, obj1); shallowCopy.b.c = 3; console.log(obj1.b.c); // 输出: 3
-
扩展运算符
const obj1 = { a: 1, b: { c: 2 } }; // 展开运算符(Spread Operator)是 JavaScript 中的一种语法,用于将可迭代对象(如数组或字符串)展开为独立的元素。它使用三个连续的点号(...)作为操作符。 // 展开运算符可以在多种情况下使用,包括数组、对象和函数调用等。 const shallowCopy = { ...obj1 }; shallowCopy.b.c = 3; console.log(obj1.b.c); // 输出: 3
深拷贝
深拷贝会递归复制对象及其所有嵌套的属性,确保新对象与原对象完全独立。常用方法包括:
-
JSON.stringify() 和 JSON.parse()
const obj1 = { a: 1, b: { c: 2 } }; const deepCopy = JSON.parse(JSON.stringify(obj1)); deepCopy.b.c = 3; console.log(obj1.b.c); // 输出: 2
-
递归函数
function deepClone(obj) { if (obj === null || typeof obj !== 'object') return obj; const copy = Array.isArray(obj) ? [] : {}; for (let key in obj) { copy[key] = deepClone(obj[key]); } return copy; }
在JavaScript中,使用浅拷贝和深拷贝的场景各有不同:
使用浅拷贝的场景
简单数据复制:当对象只包含基本数据类型(如字符串、数字等),可以使用浅拷贝。
const original = { a: 1, b: 2 }; const copy = { ...original };
组件状态管理:在React等框架中,更新状态时使用浅拷贝来保持性能。
setState(prevState => ({ ...prevState, newProp: value }));
合并对象:合并多个对象时,浅拷贝可以快速实现。
const merged = Object.assign({}, obj1, obj2);
使用深拷贝的场景
复杂对象处理:当对象包含嵌套的引用类型,需要独立复制时。
const deepCopy = JSON.parse(JSON.stringify(complexObj));
状态管理:在Redux等状态管理库中,深拷贝可以防止状态不小心被修改。
const newState = deepClone(state);
数据处理:在处理API返回的复杂数据结构时,确保原始数据不被改变。
const processedData = deepClone(apiResponse);
总结:
- 浅拷贝 适合简单对象和性能优化场景。
- 深拷贝 用于复杂数据结构,确保数据完整性和独立性。
三、总结
- 浅拷贝:复制对象的引用,对于引用类型的属性会共享内存。
- 深拷贝:递归复制对象,确保独立性,常用于需要完整复制对象及其嵌套属性的场景。