深拷贝的实现方式

举报
新手上路001 发表于 2021/10/31 22:32:44 2021/10/31
【摘要】 通过实际案例,学习深克隆与浅克隆的区别,及深克隆的实现方法。

概念

复制对象后,如果修改了原对象或新对象的数据,造成了对其他对象的数据也同时发生了变化的现象,就是浅拷贝;对象之间仍然存在关联。

如果复制后的对象与原对象,无论数据如何变化,都不会对其它对象带来变化,就是深拷贝;对象之间已经毫无关系。

1、创建用于实验的类

class User{
    int age;
    Name name;

    public int getAge() {
        return age;
    }

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

    public Name getName() {
        return name;
    }

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

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

class Name{
    String first;
    String second;

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }

    @Override
    public String toString() {
        return "Name{" +
                "first='" + first + '\'' +
                ", second='" + second + '\'' +
                '}';
    }
}

2、赋初值

public class TestDeepClone {
    public static void main(String[] args) {
        //初始化对象
        User user = new User();
        user.setAge(25);
        Name name = new Name();
        name.setFirst("li");
        name.setSecond("si");
        user.setName(name);

        System.out.println("======原始对象=============");
        System.out.println(user);
    }
}

3、对象赋值

代码片段如下:

System.out.println("======简单赋值=============");
User user2 = user;
//修改原始对象属性值
user.setAge(27);
user.getName().setFirst("zhang");
System.out.println("源对象:" + user);
System.out.println("新对象:" + user2);

这种方式,会将原对象的内存地址赋值给新对象,因此,新对象和原对象指向的是同一块内存,如果使用 user == user2, 将会得到 true;

此时如果修改任意一个对象,都将发生变化。

4、浅克隆

如果实现对象克隆,要克隆的类需要实现 Cloneable 接口并重写Object类的 clone()方法,在这里,我们需要对User类进行修改

class User implements Cloneable{
    int age;
    Name name;

    public int getAge() {
        return age;
    }

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

    public Name getName() {
        return name;
    }

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

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

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

在clone()方法中,我们仅对User对象实现了克隆,但是没有对User类下的属性类Name进行克隆;

执行克隆后,新User对象下的Name属性与原对象下的Name属性,仍然指向同一块内存,如果Name属性发生变更,所有克隆对象的Name属性都会变化。

此即为浅克隆。

代码片段如下:

System.out.println("======浅拷贝=============");
//恢复对象原始值
user.setAge(25);
user.getName().setFirst("li");
//实现对象克隆
User user3 = (User) user.clone();
//修改原始对象属性值
user.setAge(27);
user.getName().setFirst("wang");
System.out.println("源对象:" + user);
System.out.println("新对象:" + user3);

5、深克隆

5.1 所有对象都实现 Cloneable 接口并重写Object类的 clone()方法

第一步:之前已对User类实现的该接口,这里我们再对User下的对象属性 Name 实现该接口,并重写clone()方法。

class Name implements Cloneable{
    String first;
    String second;

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }

    @Override
    public String toString() {
        return "Name{" +
                "first='" + first + '\'' +
                ", second='" + second + '\'' +
                '}';
    }

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

第二步:修改User类下的clone()方法

class User implements Cloneable{
    int age;
    Name name;

    public int getAge() {
        return age;
    }

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

    public Name getName() {
        return name;
    }

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

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

    @Override
    public Object clone() {
        try {
            User u = (User) super.clone();
            //调用属性的克隆方法
            u.setName((Name) this.name.clone());

            return u;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

注意:这种方法实现的深克隆比较笨重,如果User类下有多个属性类时,要实现深克隆就需要对所有类重写clone()方法。

测试类的代码片段

System.out.println("======所有类实现克隆方法的深拷贝=============");
user.setAge(25);
user.getName().setFirst("li");
User user3 = (User) user.clone();
user.setAge(27);
user.getName().setFirst("wang");
System.out.println("源对象:" + user);
System.out.println("新对象:" + user3);

5.2 使用IO流

要使用IO流,则必须要对拷贝的类及其属性类,实现 Serializable 接口

class User implements Serializable{
    int age;
    Name name;

    public int getAge() {
        return age;
    }

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

    public Name getName() {
        return name;
    }

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

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

class Name implements Serializable{
    String first;
    String second;

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }

    @Override
    public String toString() {
        return "Name{" +
                "first='" + first + '\'' +
                ", second='" + second + '\'' +
                '}';
    }
}

5.2.1 文件IO

通过将对象序列化为字节流,将其写入到文件中,再由文件读取到内存并反序列化为对象,实现对象复制。从而为新对象在内存中开辟出了一份新的区域。

System.out.println("======IO流实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");

try {
    FileOutputStream fos = new FileOutputStream("d:/cc.txt");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(user);

    user.getName().setSecond("二");
    FileInputStream fis = new FileInputStream("d:/cc.txt");
    ObjectInputStream ois = new ObjectInputStream(fis);
    User ou = (User) ois.readObject();

    System.out.println("源对象:" + user);
    System.out.println("新对象:" + ou);
} catch (Exception e) {
    e.printStackTrace();
}

5.2.2 字节数组IO

这种方法是将对象转换为字节输出流后,再将其通过字节输入流写回到内存中,从而达到为新对象在内存中开辟出了一份新区域的目的。

System.out.println("======字节数组的IO流实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");
try {
    user.getName().setSecond("si");
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(user);
    user.getName().setSecond("三");
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    User o = (User) ois.readObject();

    System.out.println("源对象:" + user);
    System.out.println("新对象:" + o);
} catch (Exception e) {
    e.printStackTrace();
}

5.3 借助第三方工具类,通过对象到字节码的转换

此处使用的是阿里开源的 fastjson,将对象转换为字节数组后,再通过json转回对象,达到为新对象在内存中开辟出了一份新区域的目的。

使用该工具类,对要拷贝的类没有侵入性,不需要实现任何接口,一行代码就能搞定。

System.out.println("======使用第三方工具类实现深拷贝=============");
//恢复原对象属性值
user.setAge(25);
user.getName().setFirst("li");
user.getName().setSecond("si");
User parse = JSON.parseObject(JSON.toJSONBytes(user),User.class);
user.getName().setSecond("四");
System.out.println("源对象:" + user);
System.out.println("新对象:" + parse);

6、总结:

综上所述,其实要实现深拷贝就两种途径:

A 实现该类及其子类下所有属性类的clone()方法;

B 通过把类转换为底层的字节,直接从内存层面实现。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。