关于Java中Clonable接口和深拷贝详解

 更新时间:2026年01月05日 11:03:25   作者:Porunarufu  
Cloneable是一个接口,实现这个接口后,可以在类中重写Object中的clone方法,然后通过类调用clone方法进行克隆,这篇文章主要介绍了关于Java中Clonable接口和深拷贝的相关资料,需要的朋友可以参考下

前言

在 Java 中,Cloneable 接口是实现对象拷贝的核心机制之一,但它默认仅支持 浅拷贝;而 深拷贝 是基于浅拷贝的进阶需求,用于解决引用类型成员共享的问题

一、先搞懂:Cloneable接口是什么?

1. 核心定位:标记接口(Marker Interface)

Cloneable 是 Java 中的 标记接口(无任何抽象方法),仅用于告诉 JVM:“这个类的对象允许被克隆(clone())”

  • 定义(源码极简):
    public interface Cloneable {} // 无任何方法!
  • 关键依赖:克隆的核心实现并非来自 Cloneable 接口,而是来自 java.lang.Object 类的 clone() 方法
    // Object 类的 clone() 方法(protected 修饰,需重写才能公开调用)
    protected native Object clone() throws CloneNotSupportedException;

2. 浅拷贝(Shallow Copy):Cloneable的默认行为

(1)浅拷贝的定义

当对象被浅拷贝时,会创建一个 新的对象实例,但对象中的 引用类型成员变量 不会被复制(新对象和原对象共享同一个引用类型成员)。

  • 简单说:“拷贝对象本身,不拷贝对象里的‘引用子对象’”

(2)实现浅拷贝的3 个步骤

  1. 类实现 Cloneable 接口(标记允许克隆);
  2. 重写 Object 类的 clone() 方法(提升访问权限为 public,处理异常);
  3. 调用 super.clone() 完成浅拷贝(JVM 原生实现对象拷贝)。

(3)代码示例:浅拷贝实战

// 引用类型:地址(被 User 类引用)
class Address {
    private String city;

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

    // getter/setter
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
}

// 实现 Cloneable 接口,支持浅拷贝
class User implements Cloneable {
    private String name; // 基本类型包装类(不可变,浅拷贝无问题)
    private int age;     // 基本类型(浅拷贝直接复制值)
    private Address addr;// 引用类型(浅拷贝仅复制引用,共享对象)

    // 构造方法
    public User(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // 重写 clone() 方法:实现浅拷贝
    @Override
    public User clone() throws CloneNotSupportedException {
        // 调用 Object 的 clone(),JVM 会创建新对象并复制成员值
        return (User) super.clone();
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public Address getAddr() { return addr; }
}

// 测试浅拷贝
public class ShallowCopyTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);

        // 2. 浅拷贝得到新对象
        User user2 = user1.clone();

        // 3. 验证:基本类型/不可变类型的拷贝(独立)
        System.out.println(user1.getName() == user2.getName()); // true(String 不可变,共享无问题)
        user2.setName("李四");
        user2.setAge(22);
        System.out.println(user1.getName()); // 张三(原对象不受影响)
        System.out.println(user1.getAge());  // 20(原对象不受影响)

        // 4. 验证:引用类型的拷贝(共享)
        System.out.println(user1.getAddr() == user2.getAddr()); // true(同一个 Address 对象)
        user2.getAddr().setCity("上海"); // 修改 user2 的 Address
        System.out.println(user1.getAddr().getCity()); // 上海(原对象的 Address 也被修改!)
    }
}

(4)浅拷贝的问题

当对象包含 可变的引用类型成员(如 Address)时,浅拷贝会导致原对象和拷贝对象共享该成员,修改其中一个会影响另一个,破坏对象的 “独立性”—— 这就是需要深拷贝的场景。

二、深拷贝(Deep Copy):解决引用类型共享问题

1. 深拷贝的定义

深拷贝会创建一个 完全独立的新对象:不仅拷贝对象本身,还会递归拷贝对象中所有 可变的引用类型成员变量,最终新对象和原对象的引用类型成员指向不同的内存地址,互不影响。

  • 简单说:“拷贝对象本身 + 拷贝对象里所有的‘引用子对象”

2. 深拷贝的 3 种实现方式(实战常用)

方式 1:重写clone()方法,手动递归拷贝引用类型

核心思路:在浅拷贝的基础上,对每个引用类型成员也调用其 clone() 方法(需让引用类型也实现 Cloneable)。

// 步骤1:让引用类型 Address 也实现 Cloneable,支持拷贝
class Address implements Cloneable {
    private String city;

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

    // 重写 clone() 方法
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }

    // getter/setter 略
}

// 步骤2:User 类的 clone() 中,手动拷贝 Address
class User implements Cloneable {
    private String name;
    private int age;
    private Address addr;

    // 构造方法略

    @Override
    public User clone() throws CloneNotSupportedException {
        // 第一步:浅拷贝 User 对象本身
        User newUser = (User) super.clone();
        // 第二步:手动拷贝引用类型成员(深拷贝核心)
        newUser.addr = this.addr.clone(); // 递归拷贝 Address
        return newUser;
    }

    // getter/setter 略
}

// 测试深拷贝
public class DeepCopyTest1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);
        User user2 = user1.clone();

        // 验证:引用类型成员不再共享
        System.out.println(user1.getAddr() == user2.getAddr()); // false(不同 Address 对象)
        user2.getAddr().setCity("上海");
        System.out.println(user1.getAddr().getCity()); // 北京(原对象不受影响)
        System.out.println(user2.getAddr().getCity()); // 上海(新对象独立修改)
    }
}

方式 2:通过序列化(Serializable)实现深拷贝

核心思路:利用 Java 序列化将对象写入流(序列化)再从流中读取(反序列化),生成的新对象是完全独立的(无需手动递归拷贝)。

  • 优点:无需让每个引用类型都实现 Cloneable,适合复杂对象(多层引用嵌套)
  • 缺点:需要所有成员变量都实现 Serializable 接口,效率略低于手动克隆
import java.io.*;

// 步骤1:所有相关类实现 Serializable 接口(标记可序列化)
class Address implements Serializable {
    private String city;
    public Address(String city) { this.city = city; }
    // getter/setter 略
}

class User implements Serializable {
    private String name;
    private int age;
    private Address addr;

    public User(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // 步骤2:实现深拷贝工具方法(序列化+反序列化)
    public User deepCopy() throws IOException, ClassNotFoundException {
        // 1. 序列化:将对象写入字节流
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this); // 写入当前 User 对象

        // 2. 反序列化:从字节流读取新对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (User) ois.readObject(); // 生成独立的新对象
    }

    // getter/setter 略
}

// 测试序列化深拷贝
public class DeepCopyTest2 {
    public static void main(String[] args) throws Exception {
        Address addr = new Address("北京");
        User user1 = new User("张三", 20, addr);
        User user2 = user1.deepCopy();

        // 验证:完全独立
        System.out.println(user1.getAddr() == user2.getAddr()); // false
        user2.getAddr().setCity("上海");
        System.out.println(user1.getAddr().getCity()); // 北京
        System.out.println(user2.getAddr().getCity()); // 上海
    }
}

方式 3:使用第三方工具(简化开发)

实际开发中,可借助成熟工具类避免重复编码,常用的有:

  • Apache Commons LangSerializationUtils.clone()(基于序列化,无需手动写流操作);
  • Gson/Jackson:将对象转为 JSON 字符串,再转回对象(间接实现深拷贝,支持复杂对象)。

示例(Apache Commons Lang):

  1. 引入依赖(Maven):
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.14.0</version>
    </dependency>
  2. 代码实现:
    import org.apache.commons.lang3.SerializationUtils;
    
    // 所有类仍需实现 Serializable
    class User implements Serializable { /* 成员变量、构造方法略 */ }
    
    public class DeepCopyTest3 {
        public static void main(String[] args) {
            Address addr = new Address("北京");
            User user1 = new User("张三", 20, addr);
            // 一行代码实现深拷贝
            User user2 = SerializationUtils.clone(user1);
    
            System.out.println(user1.getAddr() == user2.getAddr()); // false
        }
    }

3. 深拷贝的注意事项

  • 循环引用处理:如果对象存在循环引用(如 A 引用 BB 引用 A),手动克隆会栈溢出,序列化方式(或工具类)可自动处理;
  • 不可变类型无需深拷贝:如 StringInteger 等不可变类型,浅拷贝时共享引用无问题(无法修改原值);
  • ** transient 关键字 **:序列化方式中,被 transient 修饰的成员变量不会被拷贝(需根据需求决定是否使用)。

三、浅拷贝 vs 深拷贝 核心区别

四、常见面试题 & 实战建议

1. 面试高频问题

(1)Cloneable接口有什么用?如果不实现它,调用clone()会怎样?

  • 作用:标记类的对象允许被克隆(无任何方法,仅为 JVM 提供标记);
  • 不实现后果:调用 super.clone() 时会抛出 CloneNotSupportedException(运行时异常)

(2)Object类的clone()方法是浅拷贝还是深拷贝?

默认是 浅拷贝仅复制对象的成员值(基本类型复制值,引用类型复制引用地址)

(3)深拷贝的实现方式有哪些?各自的优缺点?

  • 手动克隆:优点效率高,缺点需递归实现,复杂对象繁琐;
  • 序列化:优点无需手动处理嵌套,缺点需实现 Serializable,效率略低;
  • 第三方工具:优点简洁高效,缺点依赖外部依赖。

(4)为什么String类型在浅拷贝中不会有问题?

String 是 不可变类型(一旦创建无法修改,修改时会生成新 String 对象),浅拷贝时共享引用不会导致原对象被修改,因此无需深拷贝。

2. 实战选择建议

  • 简单对象(无可变引用类型):用 Cloneable 实现浅拷贝(高效简洁);
  • 复杂对象(多层嵌套引用):优先用第三方工具(如 Apache Commons Lang)或序列化(减少编码量);
  • 性能敏感场景:手动实现深拷贝(避免序列化的性能损耗);
  • 避免过度使用克隆:如果对象可通过构造方法创建新实例(如 new User(user1.getName(), user1.getAge(), new Address(...))),可直接用构造方法替代克隆(更易读、无接口依赖)。

总结

  • Cloneable 是 “允许克隆” 的标记接口,默认支持浅拷贝,核心依赖 Object.clone()
  • 浅拷贝适合简单对象,深拷贝解决引用类型共享问题,需通过手动递归、序列化或工具类实现;
  • 实际开发中,优先选择第三方工具(如 SerializationUtils)实现深拷贝,兼顾简洁性和稳定性。

到此这篇关于关于Java中Clonable接口和深拷贝的文章就介绍到这了,更多相关Java中Clonable接口和深拷贝内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot+MongoDB的五种操作方式

    Springboot+MongoDB的五种操作方式

    本文主要介绍了Springboot+MongoDB的五种操作方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-07-07
  • 在Java编程中定义方法

    在Java编程中定义方法

    这篇文章主要介绍了在Java编程中定义方法,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • Java连接数据库实现方式

    Java连接数据库实现方式

    文章讲述了Java连接MySQL数据库的详细步骤,包括下载和导入JDBC驱动、创建数据库和表、以及编写连接和读取数据的代码
    2024-11-11
  • Java模拟UDP通信示例代码

    Java模拟UDP通信示例代码

    这篇文章主要介绍了Java模拟UDP通信,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2020-06-06
  • SpringCloud中的Feign远程调用接口传参失败问题

    SpringCloud中的Feign远程调用接口传参失败问题

    这篇文章主要介绍了SpringCloud中的Feign远程调用接口传参失败问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • java定时任务实现的4种方式小结

    java定时任务实现的4种方式小结

    这篇文章主要介绍了java定时任务实现的4种方式小结,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringBoot项目中Controller接收两个实体的实现方法

    SpringBoot项目中Controller接收两个实体的实现方法

    本文主要介绍了SpringBoot项目中Controller接收两个实体的实现方法,主要介绍了两种方法,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • 关于weblogic部署Java项目的包冲突问题的解决

    关于weblogic部署Java项目的包冲突问题的解决

    这篇文章主要介绍了关于weblogic部署Java项目的包冲突问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01
  • java String[]字符串数组自动排序的简单实现

    java String[]字符串数组自动排序的简单实现

    下面小编就为大家带来一篇java String[]字符串数组自动排序的简单实现。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09
  • Java基础之反射原理与用法详解

    Java基础之反射原理与用法详解

    这篇文章主要介绍了Java基础之反射原理与用法,结合实例形式详细分析了java反射的相关概念、原理、使用方法与操作注意事项,需要的朋友可以参考下
    2020-02-02

最新评论