本文基于Object类clone()方法,全面梳理浅拷贝(Shallow Clone)深拷贝(Deep Clone)核心定义、实现方法、本质区别、适用场景,涵盖clone()原生实现、手动扩展深拷贝、替代方案等所有核心内容,同时明确各类实现的要求与注意事项,最终通过总结提炼关键要点。

一、前置基础:Object.clone() 原生特性与克隆前提

1. clone() 方法基础信息

clone()是Object类的native原生方法,底层由C/C++实现内存直接拷贝,效率高于手动new对象赋值,核心作用是创建当前对象的独立副本(克隆对象与原对象堆内存地址不同),方法声明如下:

// protected修饰 + 抛出受检异常CloneNotSupportedException
protected native Object clone() throws CloneNotSupportedException;

2. 实现对象克隆的两个必要前提(缺一不可)

前提1:实现Cloneable标记接口

Cloneable空接口(无任何抽象方法),仅作为JVM的克隆许可标记——告知JVM该类允许被克隆。若未实现此接口,调用clone()会直接抛出CloneNotSupportedException

前提2:重写clone()并修改访问权限为public

Object类中clone()protected修饰,结合Java权限规则:

  • 同包类可直接调用,不同包子类仅能通过super.clone()调用,外部类无法直接使用;
  • 重写时需将访问权限改为public,并建议使用协变返回值(将返回值从Object改为当前类类型),避免强制类型转换,提升代码可读性。

3. 基础克隆示例(原生浅拷贝)

public class Person implements Cloneable {
    private String name; // 基本类型包装类(不可变,按基本类型处理)
    private int age;     // 基本数据类型

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

    // 重写clone():public + 协变返回值 + 调用super.clone()
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

    // getter/setter + toString
    public void setName(String name) { this.name = name; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

// 测试:克隆对象与原对象独立
public class BaseCloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p1 = new Person("张三", 20);
        Person p2 = p1.clone();
        
        System.out.println(p1 == p2); // false:堆地址不同,独立对象
        p2.setName("李四");
        System.out.println(p1); // Person{name='张三', age=20}(修改副本不影响原对象)
        System.out.println(p2); // Person{name='李四', age=20}
    }
}

二、浅拷贝(Shallow Clone)—— Object.clone() 原生实现

1. 核心定义

浅拷贝是Object类clone()方法的默认实现逻辑,克隆时仅对对象的表层成员进行拷贝:

  • 基本数据类型(int/boolean等)不可变引用类型(String/Integer等包装类):直接复制(包装类因不可变,修改时会创建新对象,天然隔离);
  • 可变引用类型成员(自定义类/集合等):仅复制内存地址,克隆对象与原对象的引用成员指向堆内存中的同一个实际对象

2. 核心特征

  1. 表层独立,深层共享:克隆对象与原对象本身是独立的(地址不同),但二者的可变引用类型成员共享同一个对象
  2. 修改联动:修改克隆对象中可变引用类型成员的属性,会同步影响原对象(反之亦然),这是浅拷贝的核心坑点;
  3. 实现简单:仅需满足克隆两个前提,直接调用super.clone()即可,无需额外代码。

3. 完整实现代码(含问题演示)

步骤1:定义可变引用类型成员类(自定义地址类)
// 可变引用类型:修改detail属性会改变对象本身
class Address {
    private String detail; // 如"北京市海淀区"

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

    // getter/setter + toString
    public void setDetail(String detail) { this.detail = detail; }
    @Override
    public String toString() {
        return "Address{detail='" + detail + "'}";
    }
}
步骤2:定义包含引用成员的主类,实现浅拷贝
public class Person implements Cloneable {
    private String name;    // 不可变引用类型(String)
    private int age;        // 基本数据类型
    private Address addr;   // 可变引用类型(自定义类,核心坑点)

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

    // 浅拷贝:直接调用super.clone(),原生实现
    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }

    // getter/setter + toString
    public Address getAddr() { return addr; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
    }
}
步骤3:测试浅拷贝的联动修改问题
public class ShallowCloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 1. 创建原对象及引用成员
        Address addr = new Address("北京市海淀区");
        Person p1 = new Person("张三", 20, addr);
        // 2. 浅克隆得到副本
        Person p2 = p1.clone();

        // 3. 修改克隆对象的可变引用成员属性
        p2.getAddr().setDetail("上海市浦东新区");

        // 4. 结果:原对象与克隆对象的addr属性同步被修改!
        System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
        System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
    }
}

4. 浅拷贝的适用场景

仅适用于无可变引用类型成员的简单类(如仅包含基本类型/包装类/String),此类场景下浅拷贝能保证克隆对象与原对象完全隔离,且实现成本最低。

三、深拷贝(Deep Clone)—— 手动扩展的完全隔离拷贝

1. 核心定义

深拷贝是对浅拷贝的升级扩展,克隆时会递归拷贝对象的所有层级成员

  • 对基本类型/不可变引用类型:与浅拷贝一致,直接复制值;
  • 所有可变引用类型成员:不仅复制地址,还会创建该引用成员的全新实例,并递归拷贝其内部的引用成员;
    最终克隆对象与原对象在堆内存中形成两个完全独立的对象树,二者无任何内存共享,修改其一完全不影响另一。

2. 核心特征

  1. 完全隔离:克隆对象与原对象(包括所有层级的引用成员)均为独立对象,无任何内存共享;
  2. 修改无联动:无论修改哪一层级的成员属性,都不会对另一个对象产生任何影响;
  3. 需手动实现:无法通过super.clone()直接实现,需根据场景编写额外代码,保证所有引用成员都被深度克隆。

3. 两种核心实现方法(含完整代码+要求)

深拷贝有两种主流实现方式,分别适用于引用层级简单和**引用层级复杂(多层嵌套/集合)**的场景,均需满足基础克隆前提(实现Cloneable+重写clone()),且对引用成员有额外要求。

方法1:重写clone()实现递归克隆(推荐,适用于简单层级)
核心思路
  1. 先调用super.clone()完成当前对象的浅拷贝,得到基础克隆对象;
  2. 对类中每一个可变引用类型成员,单独调用其clone()方法(需保证该引用成员类也实现Cloneable+重写clone());
  3. 将克隆后的引用成员重新赋值给基础克隆对象,替换原有共享的引用地址,实现深度隔离。
实现要求
  • 主类:实现Cloneable+重写clone()(public+协变返回值);
  • 所有可变引用类型成员类:必须依次实现Cloneable+重写clone(),支持递归克隆(多层嵌套则需逐层实现)。
完整代码实现
步骤1:引用成员类(Address)实现克隆前提,支持自身克隆
class Address implements Cloneable {
    private String detail;

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

    // 关键:引用成员类必须重写clone(),改为public
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }

    // getter/setter + toString
    public void setDetail(String detail) { this.detail = detail; }
    @Override
    public String toString() {
        return "Address{detail='" + detail + "'}";
    }
}
步骤2:主类(Person)重写clone(),实现递归深克隆
public class Person implements Cloneable {
    private String name;
    private int age;
    private Address addr; // 可变引用成员

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

    // 深拷贝:浅拷贝基础上,递归克隆引用成员
    @Override
    public Person clone() throws CloneNotSupportedException {
        // 1. 先完成自身的浅拷贝,得到基础克隆对象
        Person clonePerson = (Person) super.clone();
        // 2. 递归克隆引用成员Address,替换原有共享引用(核心步骤)
        clonePerson.addr = this.addr.clone();
        // 3. 若有多个引用成员,依次递归克隆并赋值
        return clonePerson;
    }

    // getter/setter + toString
    public Address getAddr() { return addr; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
    }
}
步骤3:测试深拷贝——完全隔离,无联动修改
public class DeepCloneByRecursionTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("北京市海淀区");
        Person p1 = new Person("张三", 20, addr);
        Person p2 = p1.clone(); // 深克隆

        // 修改克隆对象的引用成员属性
        p2.getAddr().setDetail("上海市浦东新区");

        // 结果:原对象与克隆对象完全独立,互不影响
        System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
        System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='上海市浦东新区'}}
    }
}
方法2:序列化+反序列化实现(适用于复杂层级/集合)
核心思路

利用Java序列化机制,将原对象转化为字节流写入内存,再将字节流反序列化为全新对象——反序列化的过程会自动为所有层级的引用成员创建全新实例,天然实现深拷贝,无需手动递归克隆。

实现要求
  • 所有涉及的类(主类+所有层级的可变引用成员类+集合泛型类):必须实现Serializable标记接口(空接口,标记类可序列化);
  • 建议为每个序列化类显式声明serialVersionUID:避免类结构(如新增/删除字段)修改后,反序列化时抛出InvalidClassException(序列化版本号不匹配);
  • 若类中有不可序列化的成员(如transient修饰的变量),反序列化时该成员会被赋值为默认值(基本类型0/布尔false,引用类型null)。
完整代码实现
步骤1:所有类实现Serializable,声明序列化版本号
import java.io.Serializable;

// 引用成员类:实现Serializable + 显式serialVersionUID
class Address implements Serializable {
    // 序列化版本号,建议显式声明(值可自定义,如1L)
    private static final long serialVersionUID = 1L;
    private String detail;

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

    // getter/setter + toString
    public void setDetail(String detail) { this.detail = detail; }
    @Override
    public String toString() {
        return "Address{detail='" + detail + "'}";
    }
}

// 主类:实现Serializable + 显式serialVersionUID
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private Address addr; // 可变引用成员(已实现序列化)

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

    // 核心:序列化+反序列化实现深克隆(无需实现Cloneable)
    public Person deepCloneBySerialize() throws Exception {
        // 1. 序列化:将对象写入字节流(内存中)
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this); // 写入当前对象及所有层级成员

        // 2. 反序列化:从字节流读取全新对象
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Person) ois.readObject(); // 返回深度克隆的新对象
    }

    // getter/setter + toString
    public Address getAddr() { return addr; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
    }
}
步骤2:测试序列化深拷贝——支持任意复杂层级
public class DeepCloneBySerializeTest {
    public static void main(String[] args) {
        try {
            Address addr = new Address("北京市海淀区");
            Person p1 = new Person("张三", 20, addr);
            Person p2 = p1.deepCloneBySerialize(); // 序列化深克隆

            // 修改克隆对象的深层引用成员
            p2.getAddr().setDetail("广州市天河区");

            // 结果:完全隔离,无任何联动
            System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
            System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='广州市天河区'}}
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. 深拷贝的适用场景

适用于包含可变引用类型成员的类,尤其是:

  1. 引用成员为自定义类(如Address、Order等);
  2. 引用成员为集合(如List/Map,存储自定义对象);
  3. 引用成员存在多层嵌套(如Person→Order→Goods,多层引用)。

四、浅拷贝与深拷贝的核心区别(全维度对比表)

对比维度 浅拷贝(Shallow Clone) 深拷贝(Deep Clone)
实现基础 Object.clone() 原生实现,直接调用super.clone() 基于浅拷贝扩展/序列化,需手动编写额外代码
拷贝范围 仅拷贝对象表层成员,可变引用类型仅复制地址 递归拷贝所有层级成员,可变引用类型创建新实例
内存关系 克隆对象与原对象共享可变引用成员 克隆对象与原对象完全无内存共享(独立对象树)
修改影响 修改克隆对象的可变引用成员,同步影响原对象 修改任意成员,对原对象无任何影响
实现难度 简单,仅需满足克隆两个前提 较复杂,需递归克隆/序列化,依赖引用成员配合
对引用成员的要求 无额外要求(无需实现Cloneable/Serializable) 递归克隆:引用成员需实现Cloneable+重写clone()
序列化:所有类需实现Serializable
效率 高(native方法直接内存拷贝,无额外开销) 较低(递归克隆有方法调用开销,序列化有IO/反射开销)
适用场景 无可变引用类型的简单类 包含可变引用类型/多层嵌套/集合的复杂类
final成员兼容 兼容(仅复制地址,无需修改final引用) 递归克隆:不兼容final可变引用成员(无法替换引用)
序列化:兼容(自动创建新实例赋值)

五、深拷贝的替代方案:拷贝构造器(实际开发首选)

由于clone()实现深拷贝存在实现繁琐、final成员限制、引用成员配合要求高等问题,实际开发中更推荐使用拷贝构造器实现对象拷贝,无需依赖Cloneable/Serializable接口,灵活控制浅/深拷贝,且代码可读性更高。

1. 拷贝构造器定义

在类中定义一个参数为当前类对象的构造器,在构造器中手动复制原对象的所有成员变量

  • 基本类型/不可变引用类型:直接复制值;
  • 可变引用类型:可选择直接复制地址(浅拷贝)创建新实例(深拷贝),灵活控制,无需依赖引用成员的克隆/序列化实现。

2. 完整代码实现(灵活实现深拷贝)

// 引用成员类:无需实现任何标记接口
class Address {
    private String detail;

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

    // 可选:为引用成员添加拷贝构造器,方便主类深拷贝
    public Address(Address original) {
        this.detail = original.detail;
    }

    // getter/setter + toString
    public void setDetail(String detail) { this.detail = detail; }
    @Override
    public String toString() {
        return "Address{detail='" + detail + "'}";
    }
}

// 主类:通过拷贝构造器实现深拷贝
public class Person {
    private String name;
    private int age;
    private Address addr;

    // 普通构造器
    public Person(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    // 核心:拷贝构造器(实现深拷贝)
    public Person(Person original) {
        // 基本类型/不可变引用类型:直接复制值
        this.name = original.name;
        this.age = original.age;
        // 可变引用类型:创建新实例(深拷贝),浅拷贝则直接this.addr = original.addr
        this.addr = new Address(original.addr);
    }

    // getter/setter + toString
    public Address getAddr() { return addr; }
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", addr=" + addr + "}";
    }
}

// 测试:拷贝构造器深拷贝,完全隔离
public class CopyConstructorTest {
    public static void main(String[] args) {
        Address addr = new Address("北京市海淀区");
        Person p1 = new Person("张三", 20, addr);
        Person p2 = new Person(p1); // 调用拷贝构造器,实现深拷贝

        p2.getAddr().setDetail("深圳市南山区");
        System.out.println("原对象:" + p1); // 原对象:Person{name='张三', age=20, addr=Address{detail='北京市海淀区'}}
        System.out.println("克隆对象:" + p2); // 克隆对象:Person{name='张三', age=20, addr=Address{detail='深圳市南山区'}}
    }
}

3. 拷贝构造器的核心优势

  1. 无接口依赖:无需实现Cloneable/Serializable,避免接口约束;
  2. 无异常处理:无需捕获CloneNotSupportedException/IO异常,代码更简洁;
  3. 灵活可控:手动控制每个成员的拷贝方式,浅/深拷贝自由切换;
  4. 兼容final成员:可直接为final可变引用成员创建新实例赋值,无clone()的final限制;
  5. 可读性高:代码逻辑直观,无需理解递归克隆/序列化的底层细节。

六、关键注意事项(避坑指南)

  1. 不可变引用类型无浅拷贝问题:String、Integer、Long等包装类是不可变类,修改其值时会创建新对象,因此即使浅拷贝,克隆对象与原对象的该成员也天然隔离,无需深度克隆;
  2. final可变引用成员的限制:若类中有final修饰的可变引用成员,无法通过递归克隆实现深拷贝(final引用赋值后不可修改,无法替换为克隆后的新引用),此时推荐使用序列化拷贝构造器
  3. 集合的克隆注意事项:Java集合(如ArrayList/HashMap)的clone()方法也是浅拷贝——仅复制集合本身,集合中的元素仍为引用共享,若需集合深拷贝,需遍历元素并逐个克隆/新建;
  4. transient关键字的影响:序列化实现深拷贝时,transient修饰的成员会被跳过,反序列化时赋值为默认值,若需保留该成员,请勿使用transient;
  5. native方法的效率优势:Object.clone()是native方法,直接操作内存拷贝,效率高于拷贝构造器,但实际开发中,拷贝构造器的灵活性远大于效率差异,因此仍是首选;
  6. 克隆对象的equals()重写:若需判断克隆对象与原对象的内容是否一致,需重写equals()方法(默认比较地址),否则p1.equals(p2)始终返回false。

七、核心总结

  1. 克隆的基础前提:使用Object.clone()实现拷贝,必须实现Cloneable接口+重写clone()并改为public,否则会抛出异常且无法外部调用;
  2. 浅拷贝核心:Object.clone()原生实现,仅拷贝表层成员,可变引用类型共享地址,修改联动,适用于无可变引用的简单类,实现简单、效率高;
  3. 深拷贝核心:完全隔离原对象与克隆对象,递归拷贝所有层级引用成员,修改无联动,适用于含可变引用/多层嵌套的复杂类,需手动实现(递归克隆/序列化),对引用成员有额外要求;
  4. 浅深拷贝核心区别是否为可变引用类型创建新实例——浅拷贝仅复制地址,深拷贝创建新实例,这是二者所有差异的根源;
  5. 深拷贝实现选择:简单层级用递归克隆,复杂层级/集合用序列化,实际开发首选拷贝构造器(灵活、无约束、兼容性强);
  6. 关键避坑点:不可变类无浅拷贝问题,final可变引用成员避免使用递归克隆,集合clone()仍是浅拷贝;
  7. clone()的定位:Java原生的高效拷贝方案,但使用约束多、坑点多,实际开发中若非追求极致效率,优先选择更灵活的拷贝构造器。

简单来说:浅拷贝是“表层复制,深层共享”,深拷贝是“全量复制,完全隔离”,二者的选择取决于类中是否包含可变引用类型成员,而拷贝构造器则是平衡“实现难度、灵活性、兼容性”的最优解。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐