深拷贝的实现方式

2022-11-13 13:34:03 浏览数 (1)

现象

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

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

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

0 人点赞