Java对象的浅拷贝和深拷贝

一、什么是浅拷贝和深拷贝

Java中的对象拷贝(Object Copy):指的是将一个对象的所有属性(成员变量)拷贝到另一个有着相同类类型的对象中去,主要分为浅拷贝(Shallow Copy)、深拷贝(Deep Copy)。

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。简而言之,浅拷贝只是传递地址指向,新的对象并没有对引用数据类型创建内存空间

深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。简而言之,深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间

二、分别在什么场景下使用浅拷贝和深拷贝

在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用现有对象的部分或全部数据。深拷贝相比于浅拷贝速度较慢并且花销较大。

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。简而言之,需要复用现有对象的基本类型的成员变量数据时,浅拷贝即可

对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。简而言之,需要复用现有对象的引用类型的成员变量数据时,需要使用深拷贝

 

三、实现浅拷贝和深拷贝的方法

浅拷贝的实现方式:

1、通过拷贝构造方法实现浅拷贝:

拷贝构造方法指的是该类的构造方法参数为该类的对象。使用拷贝构造方法可以很好地完成浅拷贝,直接通过一个现有的对象创建出与该对象属性相同的新的对象。

2、通过重写clone()方法进行浅拷贝:

Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。但是需要注意:1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

参考代码如下:对Student类的对象进行拷贝,直接重写clone()方法,通过调用clone方法即可完成浅拷贝。

package com.stqin.study.java;

/**
 * clone方法实现浅拷贝
 */
public class ShallowCopy {
    public static void main(String[] args) {
        Age a = new Age(20);
        Student stu1 = new Student("Bob", a, 175);

        // 通过调用重写后的clone方法进行浅拷贝
        Student stu2 = (Student) stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());

        // 尝试修改stu1中的各属性,观察stu2的属性有没有变化
        stu1.setName("Tim");
        // 改变age这个引用类型的成员变量的值
        a.setAge(99);
        // 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
        // stu1.setaAge(new Age(99));
        stu1.setLength(216);
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

/**
 * 创建年龄类
 */
class Age {
    private int age;

    public Age(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return this.age + "";
    }
}

/**
 * 创建学生类
 */
class Student implements Cloneable {
    // 学生类的成员变量(属性),其中一个属性为类的对象
    private String name;
    private Age aage;
    private int length;

    // 构造方法,其中一个参数为另一个类的对象
    public Student(String name, Age a, int length) {
        this.name = name;
        this.aage = a;
        this.length = length;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Age getaAge() {
        return this.aage;
    }

    public void setaAge(Age age) {
        this.aage = age;
    }

    public int getLength() {
        return this.length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    // 设置输出的字符串形式
    @Override
    public String toString() {
        return "姓名是: " + this.getName() + ", 年龄为: " + this.getaAge().toString() + ", 长度是: " + this.getLength();
    }

    // 重写Object类的clone方法
    @Override
    public Object clone() {
        Object obj = null;
        // 调用Object类的clone方法,返回一个Object实例
        try {
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

 

深拷贝的实现方式:

1、通过重写clone方法来实现深拷贝

与通过重写clone方法实现浅拷贝的基本思路一样,只需要为对象图的每一层的每一个对象都实现Cloneable接口并重写clone方法,最后在最顶层的类的重写的clone方法中调用所有的clone方法即可实现深拷贝。简单的说就是:每一层的每个对象都进行浅拷贝=深拷贝。

参考代码如下:

package com.stqin.study.java;

/**
 * 层次调用clone方法实现深拷贝
 */
public class DeepCopy {
    public static void main(String[] args) {
        Age a = new Age(20);
        Student stu1 = new Student("Bob", a, 175);

        // 通过调用重写后的clone方法进行浅拷贝
        Student stu2 = (Student) stu1.clone();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());

        // 尝试修改stu1中的各属性,观察stu2的属性有没有变化
        stu1.setName("Tim");
        // 改变age这个引用类型的成员变量的值
        a.setAge(99);
        // 使用这种方式修改age属性值的话,stu2是不会跟着改变的。因为创建了一个新的Age类对象而不是改变原对象的实例值
        // stu1.setaAge(new Age(99));
        stu1.setLength(216);
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

/**
 * 创建年龄类
 */
class Age implements Cloneable {
    private int age;

    public Age(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return this.age + "";
    }

    @Override
    public Object clone() {
        Object obj = null;
        try {
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

/**
 * 创建学生类
 */
class Student implements Cloneable {
    // 学生类的成员变量(属性),其中一个属性为类的对象
    private String name;
    private Age aage;
    private int length;

    // 构造方法,其中一个参数为另一个类的对象
    public Student(String name, Age a, int length) {
        this.name = name;
        this.aage = a;
        this.length = length;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Age getaAge() {
        return this.aage;
    }

    public void setaAge(Age age) {
        this.aage = age;
    }

    public int getLength() {
        return this.length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    @Override
    public String toString() {
        return "姓名是: " + this.getName() + ", 年龄为: " + this.getaAge().toString() + ", 长度是: " + this.getLength();
    }

    // 重写Object类的clone方法
    @Override
    public Object clone() {
        Object obj = null;
        // 调用Object类的clone方法——浅拷贝
        try {
            obj = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        // 调用Age类的clone方法进行深拷贝
        // 先将obj转化为学生类实例
        Student stu = (Student) obj;
        // 学生类实例的Age对象属性,调用其clone方法进行拷贝
        stu.aage = (Age) stu.getaAge().clone();
        return obj;
    }
}

2、通过对象序列化实现深拷贝

虽然层次调用clone方法可以实现深拷贝,但是显然代码量实在太大。特别对于属性数量比较多、层次比较深的类而言,每个类都要重写clone方法太过繁琐。

将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

参考代码如下:

package com.stqin.study.java;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * 通过序列化实现深拷贝
 */
public class DeepCopyBySerialization {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Age a = new Age(20);
        Student stu1 = new Student("Bob", a, 175);
        // 通过序列化方法实现深拷贝
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(stu1);
        oos.flush();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        Student stu2 = (Student) ois.readObject();
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
        // 尝试修改stu1中的各属性,观察stu2的属性有没有变化
        stu1.setName("Tim");
        // 改变age这个引用类型的成员变量的值
        a.setAge(99);
        stu1.setLength(216);
        System.out.println(stu1.toString());
        System.out.println(stu2.toString());
    }
}

/**
 * 创建年龄类
 */
class Age implements Serializable {
    private int age;

    public Age(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return this.age + "";
    }
}

/**
 * 创建学生类
 */
class Student implements Serializable {
    // 学生类的成员变量(属性),其中一个属性为类的对象
    private String name;
    private Age aage;
    private int length;

    // 构造方法,其中一个参数为另一个类的对象
    public Student(String name, Age a, int length) {
        this.name = name;
        this.aage = a;
        this.length = length;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Age getaAge() {
        return this.aage;
    }

    public void setaAge(Age age) {
        this.aage = age;
    }

    public int getLength() {
        return this.length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    // 设置输出的字符串形式
    @Override
    public String toString() {
        return "姓名是: " + this.getName() + ", 年龄为: " + this.getaAge().toString() + ", 长度是: " + this.getLength();
    }
}

 

 

### Java浅拷贝深拷贝的区别及实现方式 #### 1. **基本概念** 在Java编程中,对象的拷贝分为两种主要形式:浅拷贝深拷贝。这两种拷贝方式的核心差异在于它们对待对象内部引用字段的不同处理策略[^1]。 - 浅拷贝是指创建一个新的对象,并将原对象中的所有字段按位复制到新对象中。如果字段是基础数据类型,则直接复制其值;如果是引用类型,则只复制引用地址而不克隆实际的对象实例[^5]。 - 深拷贝不仅会复制对象本身,还会递归地复制该对象所包含的所有子对象,从而确保源对象及其所有嵌套对象与目标对象完全独立[^3]。 --- #### 2. **实现方式** ##### (1)**浅拷贝** 在Java中,默认情况下通过继承`Object`类并覆盖其受保护的方法`clone()`即可实现浅拷贝。具体步骤如下: - 被复制的类需实现`Cloneable`接口以表明支持克隆操作; - 重写`clone()`方法,在其中调用`super.clone()`完成初步的字段复制过程[^2]。 下面给出一个简单的示例代码片段用于演示如何执行浅拷贝: ```java class Person implements Cloneable { private String name; private Address address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } // Getter and Setter methods omitted for brevity. } ``` 在这个例子当中,当我们尝试克隆一个Person实例时,虽然name属性会被正常复制成新的String对象,但由于address是一个复合型成员变量(即另一个类Address的实例),所以实际上只是简单地把原有的reference赋给了新产生的person object而已[^4]。 --- ##### (2)**深拷贝** 为了达到真正的深拷贝效果,除了遵循上面提到的步骤外,还需要额外采取措施去分别处理那些非基本类型的成员变量。通常有两种通用做法可供选择: - **手动逐个克隆**: 在自定义的`clone()`方法里面针对每一个非基本类型的field单独调用相应的克隆逻辑。 ```java @Override protected Object clone() throws CloneNotSupportedException { Person clonedPerson = (Person) super.clone(); clonedPerson.address = (Address) this.address.clone(); // Assuming Address also overrides clone(). return clonedPerson; } ``` 这里假设Address也已经适当地覆写了自身的`clone()`函数[^3]。 - **序列化序列化技术**: 利用Java内置的对象流机制自动完成深层次的数据分离工作。这种方法无需显式修改原有业务模型就能轻松获得完整的副本,但前提是参与序列化的各个组件都必须标记为Serializable才行。 ```java public static <T extends Serializable> T deepCopy(T obj) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); out.writeObject(obj); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bis); return (T)in.readObject(); } ``` 上述辅助工具接受任意可串行化的输入参数obj并通过中间媒介暂存后再还原出来形成最终的结果[^3]。 --- ### 总结表格对比 | 特性 | 浅拷贝 | 深拷贝 | |--------------------|---------------------------------------------|----------------------------------------------| | 定义 | 创建新对象并将原始对象的部分内容复制过去 | 将整个对象树全部重新构建一遍 | | 引用关系 | 子对象仍共享同一份实体 | 各级节点均拥有各自独立的存在 | | 实现难度 | 较易 | 更加复杂 | | 效率 | 高 | 相对较低 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值