> 技术文档 > Java 中的浅拷贝与深拷贝:原理、实现与区别

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.addru1.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 代码。