深拷贝与浅拷贝

我们知道Java中数据类型分为基本数据类型以及引用数据类型。

一,数据类型的分类

基本数据类型的特点:直接存储在栈(stack)中的数据
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

浅拷贝与深拷贝的区别
  • 浅拷贝:拷贝对象与原对象的引用时同一个对象

  • 深拷贝:拷贝对象与原对象的引用为不同对象

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

引用数据类型使用在栈中,引用变量指向堆中实际存放数据的地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

二,深拷贝与浅拷贝

  • 首先需要明白深拷贝与浅拷贝都是针对已有的对象进行操作的
  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。(拷贝对象与原对象的引用时同一个对象)
  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。(拷贝对象与原对象的引用为不同对象)
  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
    在这里插入图片描述

从内存的角度(栈与堆)分析
在这里插入图片描述

下面直接上例子吧

        String[] str = {"qi","jian"};
        String[] s = str;//这就是一个简单的浅拷贝

其中s与str都是指向了同一块内存空间,也就是说str只是把自己的一份地址拷贝给了s,并没有对数据进行拷贝一份给s.

接下来试试深度克隆。


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChildClass {
    private String name;
    private  int age;
}
import lombok.Data;
@Data
public class FatherClass implements Cloneable{
    private String name;

    private int age;

    public ChildClass child;


    @Override
    protected Object clone()  {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        FatherClass fatherA = new FatherClass();
        fatherA.setName("qijian");
        fatherA.setAge(18);
        FatherClass fatherB = (FatherClass) fatherA.clone();

        System.out.println(fatherB==fatherA);
        System.out.println(fatherA.hashCode());
        System.out.println(fatherB.hashCode());
        System.out.println(fatherA.getName());
        System.out.println(fatherB.getName());

        /**
         * 输出:
         * false
         * 972144312
         * 972144312
         * qijian
         * qijian
         */
        System.out.println("##########################");
        fatherA.child = new ChildClass();
        fatherA.child.setName("son");
        fatherA.child.setAge(1);
        FatherClass fatherC = (FatherClass) fatherA.clone();
        System.out.println(fatherC==fatherA);
        System.out.println(fatherA.hashCode());
        System.out.println(fatherC.hashCode());
        System.out.println(fatherA.getName());
        System.out.println(fatherC.getName());
        System.out.println(fatherA.child == fatherC.child);
        System.out.println(fatherA.child.hashCode()==fatherC.child.hashCode());
        System.out.println(fatherA.child.hashCode());
        System.out.println(fatherC.child.hashCode());

        /**输出:
         * false
         * 978877645
         * 978877645
         * qijian
         * qijian
         * true
         * true
         * 6733376
         * 6733376
         */

    }
}

从上面的代码中可以发现我一度的想要通过Object.clone()方法来实现深度拷贝,之后也就以失败而告终。最后终于去看里看源码。源码中对clone() 方法是这样说的

The method clone for class Object performs a specific cloning operation. First, if the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown. Note that all arrays are considered to implement the interface Cloneable and that the return type of the clone method of an array type T[] is T[] where T is any reference or primitive type. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation.

大致的译文就是:

Object类的 clone 方法执行特定的克隆操作。首先,如果该对象的类没有实现 Cloneable 接口,则抛出 CloneNotSupportedException 异常。值得注意的是所有的数组都被认为是实现 Cloneable 接口的,并且数组类型T[]的 clone 方法的返回类型是T[],其中T是任何引用或原始类型。否则,该方法创建该对象的类的一个新实例,并使用该对象对应字段的内容初始化其所有字段,就像通过赋值一样;字段的内容本身没有被克隆。因此,该方法执行该对象的“浅拷贝”,而不是“深拷贝”操作。

所以我们知道:Object的clone()方法是浅拷贝的,就上面方法的克隆体中 return super.clone(); 不管我是怎么努力都是做不到深度克隆的。

实现深度克隆的方法:

  1. 实现方法一:

    利用构造函数实现深拷贝

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public class AddressConstruct {
        private String address1;
        private String address2;
    }
    
    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public class Stu {
        private String name;
    
        private AddressConstruct address;
    
        public static void main(String[] args) {
    
            AddressConstruct address = new AddressConstruct("nanchang","ganzhou");
    
            Stu stu = new Stu("qijian",address);
    
            // 调用构造函数进行深拷贝
            Stu copyStu = new Stu(stu.getName(), new AddressConstruct("shenzhen","ganzhou"));
    
            System.out.println(stu+"\n"+copyStu +"\n"+"stu==copy?:"+(stu==copyStu));
    
            System.out.println(stu.getAddress()+"\n"+copyStu.getAddress()+"\n"+"stu.getAddress()==copyStu.getAddress()?:"+(stu.getAddress()==copyStu.getAddress()));
    
        }
    
    
    }
    
  2. 实现方法二:

    重载clone()方法实现深度拷贝

    import lombok.AllArgsConstructor;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import lombok.Setter;
    
    @Setter
    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public class AddressClone  implements Cloneable{
        private String address1;
        private String address2;
    
        @Override
        public AddressClone clone() throws CloneNotSupportedException {
            return (AddressClone)super.clone();
        }
    }
    
    
    import lombok.*;
    
    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public class StuClone implements Cloneable{
    
        private String name;
    
        private AddressClone address;
    
        /**
         *
         * @return
         * @throws CloneNotSupportedException
         * Object类中的clone()方法:
         * protected native Object clone() throws CloneNotSupportedException;
         * 克隆时需要注意的几个关键问题:
         * 几乎肯定要调用super.clone(),以及注意将克隆设为 public。
         */
        @Override
        public StuClone clone() throws CloneNotSupportedException {
    
            //需要注意的是super.clone()是浅拷贝。
            //这里应该使用AddressClone的clone方法。
            StuClone stu = (StuClone) super.clone();
            stu.setAddress(this.getAddress().clone());
    
            return stu;
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            AddressClone address = new AddressClone("nanchang","ganzhou");
            StuClone stu = new StuClone("qijian",address);
    
            StuClone copystu = stu.clone();
    
            System.out.println(stu + "\n" +copystu);
    
            System.out.println("stu.getAddress()==copystu.getAddress()?:"+(stu.getAddress()==copystu.getAddress()));
    
        }
    }
    
  3. 序列化与反序列化

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class AdressSerializable implements Serializable {
    private String address1;
    private String address2;
}
import lombok.*;

import java.io.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class StuSerializable implements Serializable {

    private String name;

    private AdressSerializable address;

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        StuSerializable stu = new StuSerializable("qijian",new AdressSerializable("nanchang","ganzhou"));

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\stu.txt"));
        oos.writeObject(stu);

        oos.close();
        System.out.println(stu+" \n " );

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\stu.txt"));

        Object o = ois.readObject();
        ois.close();
        StuSerializable stu1 = (StuSerializable) o;
        System.out.println(stu1);

        System.out.println(stu==stu1);

    }

}

参考:点击

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值