跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Java 对象赋值与 clone 方法:浅拷贝与深拷贝解析

Java 中自定义类型赋值的机制及 clone 方法的使用。默认赋值仅复制引用,导致对象共享内存。通过实现 Cloneable 接口并重写 clone 方法可实现对象复制。Object 类的 clone 默认为浅拷贝,即基本类型复制,引用类型仍共享地址。若需完全独立副本,需手动递归克隆引用对象以实现深拷贝。深拷贝确保原对象与拷贝对象在引用字段上互不影响,适用于复杂对象的独立复制场景。

DotNetGuy发布于 2026/3/28更新于 2026/10/24105 浏览
Java 对象赋值与 clone 方法:浅拷贝与深拷贝解析

1. 什么是自定义类型的赋值?

什么是赋值呢?

// 创建一个变量 a,值为 10
int a = 10;
// 把 a 的值赋值给 b
int b = a;
// 他们两个的值一样
System.out.println("a: " + a);
System.out.println("b: " + b);

在这里插入图片描述

int 是 Java 中的内置类型,我们自己创建的自定义类型(类)可以赋值吗?

同学们看一下,判断这是我们自定义类型的赋值吗?

class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        
            (, );
        
           s1;
        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}
// 创建一个对象
Student
s1
=
new
Student
"李华"
18
// 把 s1 的值赋值给 s2
Student
s2
=

答:不是的,在上面的代码中,s1 和 s2 指向的是一个空间,s2 并没有属于自己的空间。

如图:

在这里插入图片描述

我们想要的是:s2 有自己的空间,并且空间的内容和 s1 一样(如下图)

在这里插入图片描述

自定义类型的赋值是:申请一个自己单独能管理的空间。

想要完成'完美'的赋值,就需要使用 clone() 方法。

2. clone 方法

2.1 接口 Cloneable 和 Object 中的 clone 方法

Cloneable 接口: 只有实现了 Cloneable 接口的类才可以正常调用 clone() 方法。否则会抛出 CloneNotSupportedException 异常。

Cloneable 接口文档如下

在这里插入图片描述

重写 clone() 方法: Object 类的 clone() 方法是 protected,所以在自定义类中重写时,必须将其访问修饰符改为 public,才能从外部访问。

Object 类中的 Clone 方法:

在这里插入图片描述

总结: 当一个类需要写 clone 方法时,必须实现接口 Cloneable,并且重写 Object 类中的 clone 方法。

2.2 用 clone 方法解决标题 1 的问题

标题 1 的问题很严重,因为两个引用类型都指向一个空间,那么,当其中一个对象修改属性时另一个对象的属性也会被修改。

public class Main {
    public static void main(String[] args) {
        // 创建一个对象
        Student s1 = new Student("李华", 18);
        // 把 s1 的值赋值给 s2
        Student s2 = s1;
        System.out.println(s1.toString());
        System.out.println(s2.toString());
        // 修改 s1 的 name,s2 的 name 也修改了
        s1.name = "王小明";
        System.out.println("只修改 s1 的 name 为 王小明");
        System.out.println(s1.toString());
        System.out.println(s2.toString());
    }
}

在这里插入图片描述

我们要做的是,进行赋值时给 s2 也开一个空间。

在这里插入图片描述

需要在让 Student 类实现 Cloneable 接口,重写 Object 中的 clone 方法,并且调用 clone 方法。

class Student implements Cloneable {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            // 创建一个对象
            Student s1 = new Student("李华", 18);
            // 把 s1 的值赋值给 s2
            Student s2 = s1.clone();
            // 调用 object 中的 clone 方法
            System.out.println(s1.toString());
            System.out.println(s2.toString());
            // 修改 s1 的 name,s2 的 name 也修改了
            s1.name = "王小明";
            System.out.println("只修改 s1 的 name 为 王小明");
            System.out.println(s1.toString());
            System.out.println(s2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在修改 s1 的 name 时,s2 就不会随着 s1 而变动了。

在这里插入图片描述

2.3 Object 类中 clone 方法的定义和用途

Object 类中的 clone() 方法用于创建当前对象的一个副本(即对象的浅拷贝)。它是一个受保护的方法(用 protected 修饰),所以默认情况下只有 Object 类本身及其子类可以调用。在实际开发中,clone() 方法常常被重写,以便提供更灵活的复制功能。

clone 方法在 object 类的声明如下:

protected Object clone() throws CloneNotSupportedException;

说明:

  • 返回值类型:Object 类型。返回的是当前对象的副本,因此返回值需要被强制转换为具体的类型。
  • 异常:CloneNotSupportedException,如果对象的类没有实现 Cloneable 接口,调用 clone() 方法时会抛出此异常。

clone() 方法是 Object 类中定义的,用于返回一个与当前对象相同的副本。在默认实现中,它是浅拷贝,意味着它会复制对象的基本数据类型字段,但如果对象包含引用类型的字段,那么这些字段依然指向原来的对象。

3. 浅拷贝

浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身。也就是说,原对象和拷贝对象会共享对引用类型字段(例如数组、集合等)的引用。这就导致了如果你修改原对象中的引用类型字段,拷贝对象中的相同字段也会被修改。

在上述的例子中,super.clone() 方法通过 Object 类的 clone() 方法创建一个新的 Student 对象。

String name = "***":这种方式使用字符串字面量创建字符串。

String name = new String(""):这种方式会在堆内存中创建一个新的 String 对象,其内容为 "",当 name 的内容改变时,会再创建一个空间,然后把新的引用(空间地址)赋值给 name。

由于 name(name="***")和 age 都是基本数据类型和不可变对象(如 String),拷贝的过程中,name 和 age 会被直接复制。

对于 name 字段,String 是不可变的,所以即使修改原对象的 name 字段,拷贝对象的 name 字段也不会受到影响。

但是,当我们在 Student 类中添加一个自定义的引用类型,再次仿照上述 Main() 类中的 main 方法操作,发生什么呢?

class Person {
    public int number; // 电话号码

    public Person(int number) {
        this.number = number;
    }
}

class Student implements Cloneable {
    public String name;
    public int age;
    Person person;

    public Student(String name, int age, int number) {
        person = new Person(number);
        this.name = name;
        this.age = age;
    }

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

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + ", number=" + person.number + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            // 创建一个对象
            Student s1 = new Student("李华", 18, 111);
            // 把 s1 的值赋值给 s2
            Student s2 = s1.clone();
            System.out.println(s1.toString());
            System.out.println(s2.toString());
            // 修改 s1 的号码为 888
            s1.person.number = 888;
            System.out.println("只修改 s1 的号码为 888");
            System.out.println(s1.toString());
            System.out.println(s2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

使用 clone 后,s1 和 s2 不是指向各自自己的空间了吗,为什么改变 s1 中的值而 s2 中的值也发生改变了呢?

这就是浅拷贝,浅拷贝指的是在复制对象时,只复制对象的基本数据类型字段和引用字段的引用(即内存地址),并没有复制引用字段所指向的对象本身。

在 Student 类中,用一个引用变量 person,创建 s1 时 person 指向自己的空间,person 存储的是引用(空间地址,比如是 0X555),当把 s1 克隆给 s2 时 s2 中的 person 存储也是 0X555。

在这里插入图片描述

为了解决这一问题,需要自己重写 clone 方法,并且手动给 person 开空间。

4. 深拷贝

4.1 什么是深拷贝?

简单的来说,深拷贝就是解决浅拷贝存在的问题,为对象中的引用类型也开辟自己的空间,把类的赋值完美化。

在这里插入图片描述

4.2 解决浅拷贝遗留的问题

为了解决浅拷贝遗留的问题,需要在 Person 类中也写一个 clone 方法:

// 实现接口
class Person implements Cloneable {
    public int number; // 电话号码

    public Person(int number) {
        this.number = number;
    }

    // 重写 clone 方法
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // 调用 clone 方法
    }
}

需要在 Student 中改进 clone 方法的定义,使用 Person 中的 clone 方法:

public Student clone() throws CloneNotSupportedException {
    // 创建一个 Student 临时对象 tmp
    // 此时的 s.person 和 this.person 存储的相同的引用
    Student tmp = (Student) super.clone();
    // 使用 person 的 clone 方法创建一个新的对象
    // s.person 指向的是新的对象
    tmp.person = person.clone();
    return tmp;
}

改为深拷贝的整体代码:

class Person implements Cloneable {
    public int number; // 电话号码

    public Person(int number) {
        this.number = number;
    }

    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

class Student implements Cloneable {
    public String name;
    public int age;
    Person person;

    public Student(String name, int age, int number) {
        person = new Person(number);
        this.name = name;
        this.age = age;
    }

    @Override
    public Student clone() throws CloneNotSupportedException {
        // 创建一个 Student 临时对象 tmp
        // 此时的 s.person 和 this.person 存储的相同的引用
        Student tmp = (Student) super.clone();
        // 使用 person 的 clone 方法创建一个新的对象
        // s.person 指向的是新的对象
        tmp.person = person.clone();
        return tmp;
    }

    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + ", number=" + person.number + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            // 创建一个对象
            Student s1 = new Student("李华", 18, 111);
            // 把 s1 的值赋值给 s2
            Student s2 = s1.clone();
            System.out.println(s1.toString());
            System.out.println(s2.toString());
            // 修改 s1 的号码为 888
            s1.person.number = 888;
            System.out.println("只修改 s1 的号码为 888");
            System.out.println(s1.toString());
            System.out.println(s2.toString());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

运行程序:

在这里插入图片描述

显然,当 s1 改变时 s2 不会改变,s1 中的引用类型 person 和 s2 中的 person 指向不同的空间。

在这里插入图片描述

4.3 总结

关键点: 深拷贝的核心是递归地复制引用类型字段所指向的对象,而不仅仅是复制它们的引用。 浅拷贝会让原对象和拷贝对象共享引用类型字段,而深拷贝会确保原对象和拷贝对象的引用类型字段相互独立。

在上面的例子中,Student 类中的 person 字段是通过 clone() 方法进行深拷贝的,修改原对象的 person 字段不会影响拷贝对象。

深拷贝的应用:

  • 独立性:深拷贝常用于确保原对象和拷贝对象在引用类型字段上相互独立,避免它们相互影响。
  • 内存管理:在某些场景下,深拷贝可以避免对共享资源的修改导致意外的副作用。

总结: 深拷贝是对象复制中的一种高级技术,能够确保对象之间完全独立,特别适用于包含引用类型字段的复杂对象。当需要确保每个对象的字段都能被独立地复制,并且修改一个对象不会影响另一个对象时,深拷贝是必不可少。

目录

  1. 1. 什么是自定义类型的赋值?
  2. 2. clone 方法
  3. 2.1 接口 Cloneable 和 Object 中的 clone 方法
  4. 2.2 用 clone 方法解决标题 1 的问题
  5. 2.3 Object 类中 clone 方法的定义和用途
  6. 3. 浅拷贝
  7. 4. 深拷贝
  8. 4.1 什么是深拷贝?
  9. 4.2 解决浅拷贝遗留的问题
  10. 4.3 总结

更多推荐文章

查看全部
  • C++ 转 C#:核心思维转变与实战要点
  • MySQL 索引机制实战:从 ORM 到 EXPLAIN 分析
  • FPGA 开发入门:基于 Quartus 实现 LED 控制
  • 基于 Docker 部署 DeskClaw 人机协同办公平台
  • 基于 AI 智能体的费曼学习法知识助手实战
  • Whisper-WebUI 语音转文字工具部署与使用教程
  • OpenClaw 全平台卸载指南:Windows、macOS、Linux 与包管理器
  • Dify 工作流发布为 MCP Server 实战指南
  • 基于 Qwen1.5-1.8B-GPTQ-Int4 与 Chainlit 的思维导图自动生成
  • Redis IO 多路复用模型详解
  • ToDesk、顺网云与海马云运行 DeepSeek 大模型性能对比
  • 一周烧光 14 亿 Token!OpenClaw 架构设计的 10 条血泪教训
  • 图论基础与遍历算法:BFS 与 DFS
  • VSCode Copilot 登录失败的常见原因与排查方案
  • VSCode 集成 OpenAI 兼容模型配置指南
  • 基于管道通信的 Linux 进程池实现详解
  • 动态规划:打家劫舍类问题
  • AI 在医疗领域的十大应用场景解析
  • FPGA 读写 DDR4 (一) MIG IP 核控制信号
  • Python 实现十大经典排序算法详解:原理、代码与复杂度分析

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online