克隆

克隆

java中实现克隆的方式有两种,一种是实现Cloneable接口,这种方式会存在深拷贝和浅拷贝的问题。另一种是实现Serializable接口,通过序列化的方式进行克隆,反序列话会生成崭新的对象。


实现Cloneable接口

  • 浅拷贝(Object类中的clone()方法)是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对引用进行拷贝。

  • 深拷贝(或叫深克隆) 则是对对象及该对象关联的对象内容,都会进行一份拷贝。

    接下来用具体实例来看深拷贝和浅拷贝的区别。

    实体类People:

    package com.wangzhen.clone.shallow_clone;
    
    public class People implements Cloneable {
    private String name;
    private int age;
    
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    //省略get和set方法
    
    @Override
    public String toString() {
        return "People [name=" + name + ", age=" + age + "]";
    }
    
    @Override
    protected Object clone() {
        People people = null;
         try {
            people =  (People) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
             e.printStackTrace();
        }
        return people;
    }
    
    
    public static void main(String[] args) {
        People people = new People("wisdom",18);
        People clonePeople = (People) people.clone();
    
        System.out.println(people); //输出: People [name=wisdom, age=18]
        System.out.println(clonePeople); //输出: People [name=wisdom, age=18]
    
        people.setName("Klay");
    
        System.out.println(people);// People [name=Klay, age=18]
        System.out.println(clonePeople);// 输出: People [name=wisdom, age=18]
    }
    }
    

    我们将people称为原对象,将clonePeople称为克隆对象。以上例子看到,当原对象中的成员变量改变时,不会影响到克隆对象,这说明克隆对象中的成员变量是一份属于自己“私有”的数据。但是当原对象中含有对其他对象的引用时,情况就有所不同。如下例子:

    实体类Address:

    package com.wangzhen.clone.shallow_clone;
    
    public class Address {
    private String addr;
    
    public Address(String addr) {
        this.addr = addr;
    }
    
    //省略get和set方法
    
    @Override
    public String toString() {
        return "Address [addr=" + addr + "]";
    }
    
    }
    

    实体类People02:

    package com.wangzhen.clone.shallow_clone;
    
    public class People02 implements Cloneable {
    
    private String name;
    private int age;
    private Address addr;
    
    public People02(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }
    
    //省略get和set方法
    
    @Override
    public String toString() {
        return "People02 [name=" + name + ", age=" + age + ", addr=" + addr + "]";
    }
    
    @Override
    protected Object clone() {
        People02 people = null;
        try {
            people = (People02) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return people;
    }
    
    public static void main(String[] args) {
        Address addr = new Address("123456789@qq.com");
        People02 people02 = new People02("wisdom", 18, addr);
        People02 clonePeople02 = (People02) people02.clone();
    
        System.out.println(people02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
        System.out.println(clonePeople02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
    
        people02.setName("Klay");
        people02.getAddr().setAddr("987654321@qq.com");
    
        System.out.println(people02);// 输出: People02 [name=Klay, age=18, addr=Address [addr=987654321@qq.com]]
        System.out.println(clonePeople02);// 输出: People02 [name=wisdom, age=18, addr=Address [addr=987654321@qq.com]]
    
    }
    }
    

    我在People02类中引用了一个Address对象,由以上输出可知,原对象改变成员变量时,克隆对象的成员变量不会发生任何改变,而当我改变原对象的引用对象的值时,克隆对象也随之发生改变,这说明clone方法生成的克隆对象对于成员变量是重新生成一份属于自己私有的,而对于引用变量则还是指向原对象的引用。

    以上就是浅引用,那么如何实现深引用呢,需要在Address类中也实现Cloneable接口,重写clone方法。并且在People02类中的clone方法中改变address类的引用。

    改变后的Address类:

    package com.wangzhen.clone.shallow_clone;
    
    public class Address implements Cloneable {
    private String addr;
    
    public Address(String addr) {
        this.addr = addr;
    }
    
    //省略get和set方法
    
    @Override
    public String toString() {
        return "Address [addr=" + addr + "]";
    }
    
    @Override
    protected Object clone(){
        People02 people02 = null;
         try {
            people02 =  (People02) super.clone();
        } catch (CloneNotSupportedException e) {
             e.printStackTrace();
        }
        return people02;
    }
    }
    

    People02类:

    package com.wangzhen.clone.shallow_clone;
    
    public class People02 implements Cloneable {
    
    private String name;
    private int age;
    private Address addr;
    
    public People02(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }
    
    //省略get和set方法
    
    @Override
    public String toString() {
        return "People02 [name=" + name + ", age=" + age + ", addr=" + addr + "]";
    }
    
    @Override
    protected Object clone() {
        People02 people = null;
        try {
            people = (People02) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        people.addr = (Address) addr.clone();
        return people;
    }
    
    public static void main(String[] args) {
        Address addr = new Address("123456789@qq.com");
        People02 people02 = new People02("wisdom", 18, addr);
        People02 clonePeople02 = (People02) people02.clone();
    
        System.out.println(people02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
        System.out.println(clonePeople02); // 输出: People02 [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
    
        people02.setName("Klay");
        people02.getAddr().setAddr("987654321@qq.com");
    
        System.out.println(people02);// 输出: People02 [name=Klay, age=18, addr=Address [addr=987654321@qq.com]]
        System.out.println(clonePeople02);// 输出: [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
    
    }
    }
    

    以上输出可以看到,此时的克隆对象已经和原对象没有什么关系了,对原对象做任何的修改也不会影响到克隆对象。这边是深克隆。

    但是在实际应用场景中,如果一个类中存在对其他对象的引用,这个引用对象还可能引用了别的对象,造成嵌套引用。这种情况下如果使用上面实现Cloneable的方式,那简直是要爆炸。

    为了解决上面嵌套引用的问题,可以通过实现Serializable接口的方式,通过序列化和反序列化来达到克隆一个全新对象的目的。

实现Serializable接口


  • 实现Serializable接口在开发中是一种常用的方式,在javaee开发中需要所有的pojo类实现此接口,目的就是为了能够使对象在网络上得到传输。

Address类:

package com.wangzhen.clone.serializable;

import java.io.Serializable;

/**
 * Address类,People类中会引用到此类,需实现Serializable接口
 */
public class Address implements Serializable {
    private String addr;

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

    //省略get和set方法

    @Override
    public String toString() {
        return "Address [addr=" + addr + "]";
    }
}

People类:

package com.wangzhen.clone.serializable;

import java.io.Serializable;
/**
 * people类,需要实现Serializable接口
 */
public class People implements Serializable {

    /** serialVersionUID */
    private static final long serialVersionUID = 1L;

    private String name;
    private int age;
    private Address addr;

    public People(String name, int age, Address addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    //省略get和set方法

    @Override
    public String toString() {
        return "People [name=" + name + ", age=" + age + ", addr=" + addr + "]";
    }
}

克隆类:

package com.wangzhen.clone.serializable;

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

/**
 * 通过序列化反序列化实现对象的克隆
 */
public class CloneObj {
    @SuppressWarnings("unchecked")
    public static <T> T cloneObj(T obj) {

        T cloneObj = null;

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            // System.out.println("序列化完毕");

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            cloneObj = (T) ois.readObject();
            // System.out.println("反序列化完毕");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        return cloneObj;
    }
}

测试类:

package com.wangzhen.clone.serializable;

/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        Address addr = new Address("123456789@qq.com");
        People people = new People("wisdom", 18, addr);

        People cloneObj = CloneObj.cloneObj(people);

        System.out.println(people);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]
        System.out.println(cloneObj);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]

        // 对people对象中的addr进行修改
        people.getAddr().setAddr("987654321@qq.com");

        System.out.println(people);// 输出:People [name=wisdom, age=18, addr=Address [addr=987654321@qq.com]]
        System.out.println(cloneObj);// 输出:People [name=wisdom, age=18, addr=Address [addr=123456789@qq.com]]

        // 比较原对象和克隆对象以及各自的addr引用是否为同一引用
        System.out.println(people == cloneObj);// 输出:false
        System.out.println(people.getAddr() == cloneObj.getAddr());// 输出:false
    }
}

通过输出可以得知,克隆对象的所有数据都属于自己“私有”,包括值对象还是引用对象。

注:如有错误请及时指正,感谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值