Java浅拷贝和深拷贝理解笔记

Java 中什么是浅拷贝?什么是深拷贝?

一、核心理解

在 Java 中,浅拷贝深拷贝都是对象复制方式。

它们真正的区别在于:

对象内部如果包含引用类型成员变量,复制时到底是复制"引用地址",还是复制"引用对象本身"。

简单记忆:

浅拷贝:只复制外层对象,内部引用对象仍然共用。

深拷贝:外层对象复制,内部引用对象也重新复制一份。


二、先准备两个类

为了方便理解,先定义两个类:

java 复制代码
class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Student {
    String name;
    Address address;

    public Student(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

Student 类中有两个字段:

java 复制代码
String name;
Address address;

其中:

  • name 可以简单理解为普通数据;
  • address 是一个对象引用,指向一个 Address 对象。

这类"对象中包含另一个对象"的情况,就是理解浅拷贝和深拷贝的关键。


三、什么是浅拷贝?

1. 浅拷贝的含义

浅拷贝指的是:

创建一个新的外层对象,但是对象内部的引用类型字段,仍然指向原来的对象。

也就是说:

java 复制代码
Student s1 = new Student("Ken", new Address("西安"));
Student s2 = s1 的浅拷贝;

拷贝之后:

text 复制代码
s1 和 s2 是两个不同的 Student 对象
但是 s1.address 和 s2.address 指向同一个 Address 对象

2. 浅拷贝代码示例

Java 中可以通过 Object 类的 clone() 方法实现浅拷贝。

java 复制代码
class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Student implements Cloneable {
    String name;
    Address address;

    public Student(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Student clone() throws CloneNotSupportedException {
        return (Student) super.clone();
    }
}

测试代码:

java 复制代码
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("西安");
        Student s1 = new Student("Ken", address);

        Student s2 = s1.clone();

        s2.name = "Tom";
        s2.address.city = "北京";

        System.out.println(s1.name);          // Ken
        System.out.println(s1.address.city);  // 北京
    }
}

3. 为什么 s1.address.city 也变了?

因为浅拷贝之后:

java 复制代码
s1 != s2

说明 s1s2 是两个不同的 Student 对象。

但是:

java 复制代码
s1.address == s2.address

说明它们内部的 address 指向同一个 Address 对象。

所以当执行:

java 复制代码
s2.address.city = "北京";

实际上修改的是两个学生对象共同引用的那个地址对象。

因此:

java 复制代码
System.out.println(s1.address.city);

输出结果也会变成:

text 复制代码
北京

4. 浅拷贝图示

text 复制代码
s1  ---> Student对象1
          name = "Ken"
          address  ----+
                       |
s2  ---> Student对象2   |
          name = "Ken" |
          address  ----+
                       |
                    Address对象
                    city = "西安"

浅拷贝的重点是:

外层对象变成两个了,但是里面引用的对象仍然是同一个。


四、什么是深拷贝?

1. 深拷贝的含义

深拷贝指的是:

创建一个新的外层对象,并且把对象内部引用的对象也重新复制一份。

也就是说:

text 复制代码
s1 和 s2 是两个不同的 Student 对象
s1.address 和 s2.address 也是两个不同的 Address 对象

这样修改 s2.address.city,就不会影响 s1.address.city


2. 深拷贝代码示例

首先让 Address 也支持克隆:

java 复制代码
class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

然后在 Studentclone() 方法中,手动复制 address

java 复制代码
class Student implements Cloneable {
    String name;
    Address address;

    public Student(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Student clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();

        // 关键:引用类型字段也要单独复制
        student.address = this.address.clone();

        return student;
    }
}

测试代码:

java 复制代码
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("西安");
        Student s1 = new Student("Ken", address);

        Student s2 = s1.clone();

        s2.name = "Tom";
        s2.address.city = "北京";

        System.out.println(s1.name);          // Ken
        System.out.println(s1.address.city);  // 西安
        System.out.println(s2.name);          // Tom
        System.out.println(s2.address.city);  // 北京
    }
}

3. 为什么深拷贝不会互相影响?

因为深拷贝之后:

java 复制代码
s1 != s2

说明两个 Student 对象不同。

同时:

java 复制代码
s1.address != s2.address

说明两个 Address 对象也不同。

所以修改:

java 复制代码
s2.address.city = "北京";

只会影响 s2 自己的地址对象,不会影响 s1 的地址对象。


4. 深拷贝图示

text 复制代码
s1  ---> Student对象1
          name = "Ken"
          address ---> Address对象1
                       city = "西安"

s2  ---> Student对象2
          name = "Ken"
          address ---> Address对象2
                       city = "西安"

深拷贝的重点是:

外层对象复制了,里面引用的对象也复制了。


五、浅拷贝和深拷贝对比

对比点 浅拷贝 深拷贝
是否创建新的外层对象
基本类型字段 复制值 复制值
引用类型字段 复制引用地址 复制引用对象本身
内部对象是否共享 共享 不共享
修改内部对象是否互相影响 会影响 不会影响
实现难度 简单 较复杂
常见实现方式 super.clone() 手动复制引用对象、序列化、拷贝构造方法

六、再用生活例子理解

1. 浅拷贝

浅拷贝就像:

复印了一份学生档案,但是家庭住址卡片还是共用同一张。

所以如果有人修改了这张家庭住址卡片,两份档案看到的地址都会变化。


2. 深拷贝

深拷贝就像:

复印了一份学生档案,并且连家庭住址卡片也重新复印了一份。

所以修改其中一份档案里的地址卡片,不会影响另一份档案。


七、常见实现方式

1. 使用 clone() 方法

浅拷贝:

java 复制代码
@Override
protected Student clone() throws CloneNotSupportedException {
    return (Student) super.clone();
}

深拷贝:

java 复制代码
@Override
protected Student clone() throws CloneNotSupportedException {
    Student student = (Student) super.clone();
    student.address = this.address.clone();
    return student;
}

2. 使用拷贝构造方法

相比 clone(),拷贝构造方法更直观。

java 复制代码
class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }

    public Address(Address other) {
        this.city = other.city;
    }
}

class Student {
    String name;
    Address address;

    public Student(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Student(Student other) {
        this.name = other.name;
        this.address = new Address(other.address);
    }
}

测试:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Student s1 = new Student("Ken", new Address("西安"));

        Student s2 = new Student(s1);

        s2.address.city = "北京";

        System.out.println(s1.address.city);  // 西安
        System.out.println(s2.address.city);  // 北京
    }
}

这种方式本质上也是深拷贝。


3. 使用序列化实现深拷贝

如果对象层级比较复杂,也可以通过序列化和反序列化实现深拷贝。

不过这种方式性能开销较大,实际开发中要谨慎使用。

示意代码:

java 复制代码
// 将对象写入字节流,再从字节流中读出来
// 读出来的新对象与原对象完全独立

八、容易踩坑的地方

1. String 是否需要深拷贝?

一般情况下不需要特别处理 String

因为 String 是不可变对象:

java 复制代码
String name = "Ken";

当你修改字符串时,并不是修改原来的字符串对象,而是创建新的字符串对象。

所以在很多示例中,我们重点关注的是自定义引用对象,比如:

java 复制代码
Address address;

2. 只有一层深拷贝够不够?

不一定。

如果对象结构是这样的:

text 复制代码
Student
 └── Address
      └── CityInfo

那么深拷贝时,不仅要复制 Address,还要继续复制 CityInfo

否则仍然可能出现内部对象共享的问题。

所以深拷贝本质上是:

对象引用链上的所有可变引用对象,都要复制一份。


3. clone() 默认就是深拷贝吗?

不是。

Object 类的 clone() 方法默认是浅拷贝。

也就是说:

java 复制代码
super.clone();

只会复制当前对象本身,不会自动复制它内部引用的其他对象。

如果想实现深拷贝,需要你自己手动处理引用类型字段。


九、面试回答模板

可以这样回答:

Java 中浅拷贝和深拷贝都是对象复制方式。浅拷贝会创建一个新的外层对象,但是对象内部的引用类型字段仍然指向原来的对象,因此两个对象会共享内部引用对象。深拷贝不仅会复制外层对象,还会复制对象内部引用的对象,因此两个对象之间是相互独立的。Java 中 Object 类的 clone() 方法默认实现的是浅拷贝,如果要实现深拷贝,需要对引用类型字段手动再次复制,或者使用拷贝构造方法、序列化等方式实现。


十、最终总结

一句话总结:

浅拷贝复制对象本身,深拷贝复制对象本身以及它引用的对象。

再简单一点:

浅拷贝:外壳复制,里面共用。

深拷贝:外壳复制,里面也复制。

判断一个拷贝是浅拷贝还是深拷贝,关键看:

java 复制代码
原对象.引用字段 == 拷贝对象.引用字段

如果结果是 true,通常就是浅拷贝。

如果结果是 false,说明引用对象也被复制了,更接近深拷贝。

相关推荐
码不停蹄的玄黓1 小时前
线上频繁FullGC完整排查流程
java
兔老大RabbitMQ1 小时前
IDEA 打字打在光标右边 / 删除异常问题
java·ide·intellij-idea
草莓熊Lotso1 小时前
【Linux网络】深入理解 HTTP 协议(三):静态资源服务、状态码与重定向实战
linux·运维·服务器·网络·c++·http
jeffer_liu1 小时前
Spring AI 生产级实战:多模态
java·人工智能·后端·spring·大模型
码不停蹄的玄黓1 小时前
Arthas 最常用命令速查表
java
我命由我123451 小时前
Excel - Excel 查看当前单元格格式
运维·学习·职场和发展·excel·求职招聘·职场发展·学习方法
love530love1 小时前
【笔记】ComfyUI 源码部署版更新后一键修复:从手动补丁到自动化工作流
运维·人工智能·windows·笔记·python·自动化·comfyui
石榴树下的七彩鱼1 小时前
发票OCR识别API接入教程:从图像到结构化数据的完整实战(附Python/Java/PHP/JS代码)
java·python·ocr·api接口·财务自动化·石榴智能·发票ocr
hj2862511 小时前
Linux + 计算机网络全套精炼整理笔记
linux·运维