Java 中的浅拷贝与深拷贝:原理、实现与区别
引言
在 Java 开发中,对象拷贝是一个常见需求,尤其是在需要复用对象状态又不希望修改原对象时。但很多开发者在面对浅拷贝和深拷贝时容易混淆,本文将从概念、实现方式、区别对比三个维度详细解析,帮你彻底搞懂这两种拷贝方式。
一. 为什么需要对象拷贝?
先看一个简单场景:当我们需要一个与现有对象属性完全相同的新对象,并且对新对象的修改不会影响原对象时,就需要用到对象拷贝。
没有拷贝的情况下,直接赋值只是传递引用:
User u1 = new User(\"张三\", 20);User u2 = u1; // 引用传递,u1和u2指向同一个对象u2.setName(\"李四\"); System.out.println(u1.getName()); // 输出\"李四\",原对象被修改
这显然不符合 \"修改新对象不影响原对象\" 的需求,因此我们需要真正的 \"拷贝\" 而非引用传递。
二. 浅拷贝(Shallow Copy)
1. 定义
浅拷贝是指:创建一个新对象,新对象的基本数据类型属性与原对象完全相同,而引用类型属性仅复制引用地址(即新对象和原对象的引用类型属性指向同一个内存地址)。
简单说:浅拷贝只拷贝对象本身,不拷贝对象所包含的引用类型成员。
举个生活中的例子:
假设你有一张纸条(原对象),上面写着你家的地址(引用类型属性,存的是内存地址)。
浅拷贝就相当于复印了这张纸条:新纸条(新对象)上的地址和原纸条完全一样(复制了引用地址),但两张纸条指向的是同一个家(同一个引用类型对象)。
这里的 “不拷贝引用类型成员”,指的是没有复制 “家” 本身(没有创建一个新的家),只是复制了 “家的地址”。
用代码再明确一下:
原对象u1
有一个引用类型属性addr
,指向内存中的Address
对象(假设内存地址为0x123
):
User u1 = new User();u1.addr = new Address(\"北京\"); // u1.addr 存的是 0x123(指向Address对象)
浅拷贝得到u2
后:
User u2 = (User) u1.clone(); // 浅拷贝
此时:
u2
是全新的对象(内存中一个新地址,比如0x456
),这是 “拷贝了对象本身”;u2.addr
复制的是u1.addr
的值(即0x123
),所以u2.addr
和u1.addr
指向同一个Address
对象(内存中0x123
处的那个)。
也就是说:
浅拷贝对引用类型做的是 “引用的复制”(复制地址),而不是 “对象的复制”(没有新建一个Address
对象)。因此,我们说 “不拷贝对象所包含的引用类型成员”—— 这里的 “拷贝” 指的是 “创建新的引用类型对象”,而不是 “复制引用地址”。
这也是为什么修改u2.addr
的属性会影响u1.addr
:因为它们操作的是同一个Address
对象。
2. 实现方式
Java 中实现浅拷贝通常有两种方式:
- 实现
Cloneable
接口,重写clone()
方法 - 通过构造方法手动复制属性
1. 基于 Cloneable 接口实现浅拷贝
// 用户类class User implements Cloneable { private String name; // 基本类型包装类( immutable,行为类似基本类型) private int age; // 基本数据类型 private Address addr; // 引用类型 // 构造方法、getter、setter省略 // 重写clone方法实现浅拷贝 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // Object类的clone()默认是浅拷贝 }}// 地址类(引用类型)class Address { private String city; // 构造方法、getter、setter省略}// 测试类public class ShallowCopyDemo { public static void main(String[] args) throws CloneNotSupportedException { Address addr = new Address(\"北京\"); User u1 = new User(\"张三\", 20, addr); // 浅拷贝得到u2 User u2 = (User) u1.clone(); // 修改基本类型属性,不影响原对象 u2.setAge(21); System.out.println(u1.getAge()); // 输出20(原对象不变) // 修改引用类型属性,影响原对象 u2.getAddr().setCity(\"上海\"); System.out.println(u1.getAddr().getCity()); // 输出\"上海\"(原对象被修改) }}
2. 通过构造方法实现浅拷贝
// 在User类中添加拷贝构造方法public User(User user) { this.name = user.name; this.age = user.age; this.addr = user.addr; // 仅复制引用}// 使用方式User u2 = new User(u1); // 效果与clone()的浅拷贝一致
3. 浅拷贝的特点
- 优点:实现简单,性能开销小
- 缺点:引用类型属性共享内存,修改新对象的引用属性会影响原对象
- 适用场景:对象仅包含基本数据类型或不可变引用类型(如 String)
三. 深拷贝(Deep Copy)
1. 定义
深拷贝是指:创建一个新对象,不仅复制对象本身,还递归复制对象所包含的所有引用类型成员,直至所有基本数据类型。
简单说:深拷贝会生成一个完全独立的对象,新对象和原对象的所有属性(包括引用类型)都不共享内存。
2. 实现方式
深拷贝的实现相对复杂,常见方式有:
- 让引用类型成员也实现
Cloneable
接口,在clone()
中递归拷贝 - 通过序列化(Serialization)实现
- 手动嵌套拷贝(构造方法多层复制)
1. 基于 Cloneable 递归实现深拷贝
// 地址类实现Cloneableclass Address implements Cloneable { private String city; // 重写clone方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } // 构造方法、getter、setter省略}// 用户类的clone方法中递归拷贝引用类型class User implements Cloneable { private String name; private int age; private Address addr; @Override protected Object clone() throws CloneNotSupportedException { // 先浅拷贝得到基本类型 User user = (User) super.clone(); // 再手动拷贝引用类型(深拷贝关键) user.addr = (Address) this.addr.clone(); return user; } // 构造方法、getter、setter省略}// 测试深拷贝public class DeepCopyDemo { public static void main(String[] args) throws CloneNotSupportedException { Address addr = new Address(\"北京\"); User u1 = new User(\"张三\", 20, addr); User u2 = (User) u1.clone(); // 修改引用类型属性,原对象不受影响 u2.getAddr().setCity(\"上海\"); System.out.println(u1.getAddr().getCity()); // 输出\"北京\"(原对象不变) }}
2. 通过序列化实现深拷贝
import java.io.*;// 需要拷贝的类必须实现Serializable接口class User implements Serializable { private String name; private int age; private Address addr; // Address也需实现Serializable // 深拷贝方法 public User 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 (User) ois.readObject(); } // 其他代码省略}class Address implements Serializable { private String city; // 其他代码省略}
序列化方式更适合复杂对象(包含多层引用类型)
3. 深拷贝的特点
- 优点:新对象与原对象完全独立,修改互不影响
- 缺点:实现复杂,性能开销大(递归拷贝或序列化耗时)
- 适用场景:对象包含多层引用类型,且需要完全隔离的场景
四、浅拷贝与深拷贝的核心区别
五. 总结
浅拷贝和深拷贝的核心区别在于是否递归复制引用类型成员。选择哪种拷贝方式,取决于对象的结构和业务需求:追求性能且对象结构简单时用浅拷贝;需要完全隔离且对象复杂时用深拷贝。理解两者的原理和适用场景,能帮助我们写出更健壮的 Java 代码。