带你彻底掌握Java中深拷贝与浅拷贝

529 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情

觉得对你有益的小伙伴记得点个赞+关注

后续完整内容持续更新中

希望一起交流的欢迎发邮件至javalyhn@163.com

1. 先上案例

在解释深拷贝与浅拷贝前,先分别来一个浅拷贝与深拷贝的案例

1.1 浅拷贝案例

public class Thing implements Cloneable{
    //定义一个私有变量
    private ArrayList<String> array = new ArrayList<>();

    //浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Thing thing = null;
        try {
            thing = (Thing)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return thing;
    }

    //设置ArrayList的值
    public void setValue(String value) {
        this.array.add(value);
    }

    //获取ArrayList的值
    public ArrayList<String> getValue() {
        return this.array;
    }
}

在Thing类中增加一个私有变量array,通过setValue和getValue进行取值和赋值,下面定义CLient来看看浅拷贝如何操作

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //产生一个对象
        Thing thing = new Thing();
        //设置一个值
        thing.setValue("lyhn");
        //拷贝一个对象
        Thing clone = (Thing) thing.clone();
        clone.setValue("zhangsan");
        System.out.println(thing.getValue());
    }
}

image.png

为什么我一个用的thing一个用的thing的克隆对象,但是最终结果不是lyhn,而是lyhn和zhangsan呢?

这就是浅拷贝,原始对象的array和其克隆对象array共用一个内存地址,仅仅实现了thing的克隆,而属性array并没有

1.2 深拷贝案例

我们仅需要增加一行代码,就可以实现

//深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
    Thing thing = null;
    try {
        thing = (Thing)super.clone();
        //ArrayList实现了Cloneable
        this.array = (ArrayList<String>)this.array.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return thing;
}

image.png

2. 浅拷贝与深拷贝

2.1 浅拷贝

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
  3. 前面的浅拷贝案例即可证明。
  4. 浅拷贝是使用默认的clone()方法来实现。

String 类型比较特殊,他不会产生有浅拷贝带来的问题。他是没有clone方法的,JAVA希望我们把他当做基本类型使用。而且处理机制很特殊,通过字符串池(String Pool)在需要的时候才在内存中创建新的字符串。

2.2 深拷贝

  1. 复制对象的所有基本数据类型的成员变量值
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
  3. 深拷贝实现方式1:重写clone方法来实现深拷贝深拷贝
  4. 实现方式2:通过对象序列化实现深拷贝(推荐

实现方式1已经实现过了,就是上面深拷贝的案例,下面演示通过对象序列化实现深拷贝。

 public class DeepProtoType implements Serializable,Cloneable {
    public String name;

    public DeepCloneableTarget deepCloneableTarget;

    public DeepProtoType() {
    }

    //深拷贝
    //方式二
    //通过对象序列化实现深拷贝  推荐
    public Object deepClone() {
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try{
            //序列化操作
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);//当前这个对象以对象流的方式输出

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);

            DeepProtoType deepProtoType = (DeepProtoType) ois.readObject();

            return deepProtoType;
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2.3 建议

深拷贝和浅拷贝不建议混合使用,特别是在涉及类的继承时,父类有多个引用的情况就十分复杂,建议分开来实现浅拷贝与深拷贝。

3. clone与final

对象的clone与final这两个关键字是有冲突的

下面看例子

image.png

相信大家一看就知道,array被设置为了final,那还怎么重新赋值,要实现深拷贝的梦想在final关键字的威胁下破灭了,因此要想实现深拷贝,final关键字不能加。

这篇文章很简单,希望大家好好理解