java对象的浅克隆和深克隆

概述

在java的应用中,我们不免的会需要获得一个完全一模一样的对象,在操作新对象的时候不希望旧的对象被改变,如果直接加一个对相同对象的引用,那么修改新对象的时候实际上修改的是与原对象相同的内存空间,达不到我们的目的。

此时我们就需要对对象进行克隆,而关于对象的克隆,就有浅克隆和深克隆两种,浅克隆仅能满足简单的对象克隆,而较为复杂的对象,就需要用深克隆才能实现了。

1.浅克隆——Cloneable接口

浅克隆需要对象实现Cloneable接口并重写clone方法

方法体通常只需要retun (E) super.clone();即可

但这种方式如果碰到对象是复杂的引用类型,则会因复制过去的是该对象的地址造成问题

这意味着复制出来的对象和原对象共享一个对象(类似于static变量)

(1).浅克隆有关引用类型和String类

很多资料上会直接谈到浅克隆中一旦有引用型变量就不行,那我前面为什么要提到"复杂的"呢,这是因为像String这种类型的对象它虽然是引用类型,但却不会因为浅复制而受到影响,因为String类是不可变的,一旦修改String类对象的值,那么底层就相当于重新创建了一个String对象重新赋值引用,所以我们可以认为:

String这种不可变对象天然地具有浅克隆的特性。

(2).有可变类的情况怎么办

   public Employee clone() throws CloneNotSupportedException
   {
      Employee cloned = (Employee) super.clone();
      cloned.hireDay = (Date) hireDay.clone();
      return cloned;
   }

我们可以通过将可变的引用类型字段也实现Cloneable接口,并同时实现自己的clone()方法,然后再在其超类中调用该克隆方法去覆盖原有的指针指向。

2.深克隆——序列化

深复制需要对象实现Serializable接口,这里的深复制涉及到序列化的知识点

不用重写或者实现该接口的任何方法,实现这个接口仅表示该对象可以被序列化

注意:被关键字transient修饰的属性表示不想被序列化,序列化时将被忽略,数据将不存在

案例中使用字节流和对象流来实现深克隆,具体步骤在代码中以注释形式展现

3.案例代码

import java.io.*;

class Person implements Serializable,Cloneable {
    private String name;
    private Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Object deepClone() throws IOException, ClassNotFoundException {
        //创建一个 ByteArrayOutputStream 对象 baos,用于在内存中创建字节数组缓冲区。
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //创建一个 ObjectOutputStream 对象 oos,将其与 baos 关联,用于将对象写入字节流。
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        //将原对象写入 baos 中,将其转换为字节流并存储在字节数组缓冲区中。
        oos.writeObject(this);
        //调用 close() 关闭输出流,释放与输出流关联的资源,并确保数据已写入字节数组缓冲区。
        oos.close();
        //创建一个 ByteArrayInputStream 对象 bais,将其初始化为字节数组缓冲区的内容。
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        //创建一个 ObjectInputStream 对象 ois,将其与 bais 关联,用于从字节流中读取对象。
        ObjectInputStream ois = new ObjectInputStream(bais);
        //从 bais 中读取字节流,并将其反序列化为对象。
        Object obj = ois.readObject();
        //调用 close() 关闭输入流,释放与输入流关联的资源。
        ois.close();
        return obj;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

class Address implements Serializable {
    private String city;

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

    public void setCity(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }
}

public class CloneTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
        Address address = new Address("New York");
        Person person1 = new Person("John", address);
        //使用深克隆方法复制生成对象person2
        Person person2 = (Person) person1.deepClone();
        System.out.println(person1.getName());        // 输出: John
        System.out.println(person1.getAddress().getCity()); // 输出: New York
        System.out.println(person2.getName());        // 输出: John
        System.out.println(person2.getAddress().getCity()); // 输出: New York

        // 修改 person2 的 city
        person2.getAddress().setCity("London");
        // 由于深复制,person1 和 person2 拥有各自独立的 Address 对象,修改其中一个的地址不会影响另一个
        System.out.println(person1.getAddress().getCity()); // 输出: New York
        System.out.println(person2.getAddress().getCity()+"\n"); // 输出: London

        //使用浅克隆方法复制生成对象person3
        Person person3 = person1.clone();
        // 修改 person3 的 city
        person3.getAddress().setCity("London");
        // 由于浅复制,person1 和 person3 拥有相同的 Address 对象,修改其中一个的地址会直接影响另一个
        System.out.println(person1.getAddress().getCity()); // 输出: London
        System.out.println(person2.getAddress().getCity()); // 输出: London

    }
}

4.经验总结

这里使用通俗易懂的语言再次解析序列化和反序列化的过程:

1.首先我们在内存上创建通用的一个字节输出流,用于接收各种类型的流式数据;

2.我们再创建一个对象输出流去绑定前面创建的字节输出流,确定数据具体类型为对象;

3.将原对象以对象流的形式写入其绑定的字节流内存空间;

4.释放对象输出流,确保内容写入字节流

5.开始反序列化,创建一个字节输入流,绑定到字节输出流的位置上

6.创建一个对象输入流,绑定到刚才创建的字节输入流上,确保是以对象形式读取这段数据;

7.从中以Object类型读取对象数据

8.关闭对象输入流,确保内容全部写出

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值