深拷贝与浅拷贝

简介: 深拷贝与浅拷贝

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


一,数据类型的分类


基本数据类型的特点:直接存储在栈(stack)中的数据


引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里


浅拷贝与深拷贝的区别


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


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


二,深拷贝与浅拷贝


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



20210625194947371.png


从内存的角度(栈与堆)分析


20210625195010746.png



下面直接上例子吧


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()));
    }
}


  1. 实现方法二:

重载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()));
    }
}


  1. 序列化与反序列化
@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);
    }
}
相关文章
|
JSON Java API
深拷贝、浅拷贝
深拷贝、浅拷贝
90 0
|
3月前
|
JavaScript 前端开发 Java
什么是深拷贝,什么是浅拷贝
什么是深拷贝,什么是浅拷贝
77 0
|
6月前
|
编译器 C++
深拷贝和浅拷贝介绍
这篇文章讨论了C++中的数据拷贝,特别是浅拷贝和深拷贝的概念。对于基本类型和简单对象,拷贝是按位复制,即浅拷贝,类似于`memcpy()`函数的效果。当类包含动态分配的内存或其他资源时,需要显式定义拷贝构造函数以实现深拷贝,确保对象间的独立性。文中通过一个自定义的变长数组类`Array`示例说明了深拷贝的必要性,并展示了不使用深拷贝可能导致的问题。通常,如果类有指针成员,大部分情况需要深拷贝;否则,浅拷贝可能就足够了。文章还提到了在创建对象时需要预处理的情况,如记录对象创建时间或计数,这也需要深拷贝。
|
8月前
什么是深拷贝和浅拷贝哇
什么是深拷贝和浅拷贝哇
|
8月前
|
JavaScript 前端开发
浅拷贝和深拷贝
浅拷贝和深拷贝
53 2
|
C++
22 C++ - 深拷贝和浅拷贝
22 C++ - 深拷贝和浅拷贝
51 0
|
编译器 C++
C++中的深拷贝和浅拷贝介绍
对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存。例如: class Base{ public: Base(): m_a(0), m_b(0){ } Base(int a, int b): m_a(a), m_b(b){ } private: int m_a; int m_b; }; int main(){ int a = 10; int b = a; //拷贝 Base obj1(10, 20);
131 0
|
Java
浅拷贝与深拷贝
浅拷贝与深拷贝
93 0
|
前端开发
对于深拷贝与浅拷贝的理解
对于深拷贝与浅拷贝的理解
深拷贝和浅拷贝
类里面会为我们实现默认的拷贝,这个做的是值的拷贝,但是假如对象里的数据成员在堆上开辟了内存资源,如果继续浅拷贝就会导致两根指针指向同一块资源,从而产生内存泄漏问题。但是深拷贝可以解决这个问题,本文将详细介绍深拷贝与浅拷贝。