前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深拷贝的实现方式

深拷贝的实现方式

作者头像
头发还在
发布2022-11-13 13:34:03
3510
发布2022-11-13 13:34:03
举报
文章被收录于专栏:桃花源桃花源

现象

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

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

1、创建用于实验的类

代码语言:javascript
复制
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、赋初值

代码语言:javascript
复制
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、对象赋值

代码片段如下:

代码语言:javascript
复制
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类进行修改

代码语言:javascript
复制
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属性都会变化。

此即为浅克隆。

代码片段如下:

代码语言:javascript
复制
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()方法。

代码语言:javascript
复制
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()方法

代码语言:javascript
复制
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()方法。

测试类的代码片段

代码语言:javascript
复制
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 接口

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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转回对象,达到为新对象在内存中开辟出了一份新区域的目的。

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

代码语言:javascript
复制
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 通过把类转换为底层的字节码,直接从内存层面实现。

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 现象
  • 1、创建用于实验的类
  • 2、赋初值
  • 3、对象赋值
  • 4、浅克隆
  • 5、深克隆
    • 5.1 所有对象都实现 Cloneable 接口并重写Object类的 clone()方法
      • 5.2 使用IO流
        • 5.2.1 文件IO
        • 5.2.2 字节数组IO
      • 5.3 借助第三方工具类,通过对象到字节码的转换
      • 6、总结:
      相关产品与服务
      文件存储
      文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com