[知识向] __ 关于深浅拷贝

  • 前言

拷贝操作,在我们的日常使用电脑的过程中的一种十分常见的情况.但在计算机语言里,拷贝的操作其实是分为两种的.那就是深拷贝与浅拷贝.

而其实在任何编程语言里,都存在着深浅拷贝这两种概念的.java语言也不例外.今天就从java语言出发,记录一下关于深浅拷贝的学习.


  • 什么是深浅拷贝

首先我们因当搞明白这两个名词的概念,在讲深浅拷贝之前,我们先来说一说拷贝.什么是拷贝,拷贝干了什么.
以我们直观的理解,拷贝就是复制,就是将一个对象由一份变为两份

但是这样直观的方式的实现,缺失有不同的方式的,那就是深浅拷贝两种不同的方式.

我们一java语言的来理解它们,深浅拷贝必然是针对一个已有的对象进行的操作,而这个操作的目的,或者说实现的东西就如上面所说的直观解释.

我们知道,JAVA语言是一门面向对象的编程语言,它除了基本数据类型之外,还存在着引用数据类型(类的实例对象).而在我们通常使用=来进行赋值操作的时候,对于基本数据类型,实际上是拷贝它的值,而对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去.他们实际上是指向同一个对象的.

而深浅拷贝就是以这为基础而进行区分的即:

  • 浅拷贝

浅拷贝在拷贝操作的时候,只对基本数据类型进行拷贝,而在拷贝实例化对象(引用数据类型)的时候,知识进行了引用的传递,而不会新建一个对象来拷贝并保存原对象.实际上他们都指向的是同一个原对象即:.
只对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,则为浅拷贝

image.png
  • 深拷贝

深拷贝则正好与浅拷贝相反,当在拷贝引用数据类型(实例化对象)时,新建了一个对象,并复制原对象中的所有属性和方法,将原对象保存在新对象中.使原对象和新建对象成为两个相同但完全独立的两个对象.
对基本数据类型进行值传递,对引用数据类型,创建一个新对象,并复制原对象的内容,则为深拷贝

image.png

因此,其实在某种意义意义上拷贝操作并不是绝对的,因为他们在对基本数据类型拷贝的时候,操作都是一样的,所以都可称之为深拷贝,或浅拷贝.其主要区别在于后面关键的对象的拷贝上.

并且值得我们注意的是,因为浅拷贝都是指向同一个原对象的,所以它是不会耗费储存空间的,但它具有一定的联动性,当原对象有所改变时,拷贝的对象(其实就是同一个)也一样会改变.(就和桌面快捷方式一样)

而深拷贝会新建对象将原对象完全复制一份保存在新对象中,所以它是需要占用存储空间的.但它的好处就是两者相互不影响,都是相对独立的.(我们经常做的备份工作就是一种深拷贝操作)


  • Java中的clone()方法

我们知道在java语言中,所有的类都继承自Object类,在Object类中,存在着一个clone方法.它被声明为protected(同一个类或子类中可用)所以我们可以在子类中使用它.

而无论深浅拷贝,都需要重写clone方法来实现拷贝操作.

image.png

我们可以看到该方法十分简单.简单到我基本没怎么明白它的意思.

我们再看看关于该方法的注释信息:

image.png

这段注释告诉我们如果调用这个方法的对象没有实现Cloneable接口将会报CloneNotSupportedException异常.

image.png

这段注释的意思我们可以大概明白,表示Object类自身不能实现接口,所以在对象上直接调用该方法时,会抛出一个运行时异常.

image.png

我们在看看这段注释,它给我们说明了,这个clone方法是默认的是一个浅拷贝的方法,它会新建一个实例对象并初始化其中的所有字段的内容,但该字段内容的本身是不会被克隆的.因此该方法是浅拷贝操作,而不是深拷贝操作.

我们使用java语言来具体看看.
先来看看object类的clone方法(浅拷贝)的例子.

/**
 * object类中的clone方法.
 * 它实际上是一个浅拷贝操作.
 * 但它在拷贝一个对象的时候,却确实新建了一个实例化对象.
 * 但它是实际却是一次浅拷贝操作.
 */
class ShallowCopy implements Cloneable{
    private String name;
    private int age;

    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    // 构造方法.
    ShallowCopy(String name,int age){
        this.name=name;
        this.age=age;
    }
    // 创建一个输出方法
    void toimString(){
        System.out.println(this.name+this.age);
    }
    // clone方法是Object基类中提供的方法,修饰为protected,
    // 我们可以在任何继承它的类中重写实现该方法来完成clone操作.
    @Override
    // 重写object类中的clonr方法,使异常处理在方法体中进行.
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}

main方法中:

 public static void main(String[] args) {
        ShallowCopy xiaoshe = new ShallowCopy("xiaoshe", 3);
        xiaoshe.toimString();
        System.out.println(xiaoshe.hashCode());
        ShallowCopy xiaoshe2 = (ShallowCopy) xiaoshe.clone();
        xiaoshe2.toimString();
        System.out.println( xiaoshe2.hashCode());
        System.out.println((xiaoshe==xiaoshe2));
        System.out.println((xiaoshe.getName().equals(xiaoshe2.getName())));
    }

我们观察下结果:

image.png

我们可以发现,clone方法确实新建了一个实例化对象来拷贝原对象(哈希值不同,==为false,equlas方法为true).那为什么会说object类中的clone方法实际是浅拷贝操作呢,创建一个新对象来储存原对象不是深拷贝的性质吗.我们再来看一个例子:

在ShallShallowCopy类中添加一个inner成员变量(属性).并使用成员内部类的方式构建一个innerClass类.

  public innerClass inner;

    public class innerClass{
        innerClass(String inname,int inage){
            name=inname;
            age=inage;
        }
        void imString(){
            toimString();
        }
        void getname(){
            getName();
        }
        void getage(){
            getAge();
        }
    }

main方法中(加入成员inner的构建和copy):

    xiaoshe.inner = xiaoshe.new innerClass("小舍",4);
    ShallowCopy xiaoshe3 = (ShallowCopy) xiaoshe.clone();

输出测试:

        xiaoshe.inner.imString();
        System.out.println(xiaoshe.inner.hashCode());
        xiaoshe3.inner.imString();
        System.out.println(xiaoshe3.inner.hashCode());
        System.out.println((xiaoshe.inner == xiaoshe3.inner));

结果:

image.png

我们从inner的输出可以发现在这个时候,这里只对ShallShallowCopy类拷贝了一次.而其中的inner对象和copy后的inner对象实际上还是指向的一个同一个对象,只是对它的引用进行了传递而没有进行拷贝操作.

所以说深浅拷贝实际上并不是一个绝对的定义.而是根据情况来进行判断的,clone方法在copy基本数据类型时,就相当于深拷贝.只有在拷贝实例化对象时(引用数据类型)才会有两种不同的处理方式(深浅拷贝).

我们再来看看深拷贝的实现.

按照object类的clone方法的思路来考虑,我们只要让内部类innerClass也重写clone方法,让它自己也clone一份,那么实际上就解决了浅拷贝的问题.

//让内部类继承Cloneable接口并重写clone方法.
   public class innerClass implements Cloneable{
        innerClass(String inname,int inage){
            name=inname;
            age=inage;
        }
        void imString(){
            toimString();
        }
        void getname(){
            getName();
        }
        void getage(){
            getAge();
        }

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

在类中重写clone方法,实例一个对象clone原对象,并调用其inner成员的clone方法对其inner对象进行拷贝.

@Override
    protected Object clone(){

        try {
            ShallowCopy copy = (ShallowCopy) super.clone();
            copy.inner = (innerClass) this.inner.clone();
            return copy;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

结果:

image.png

可以看到,这里对inner也进行了一次拷贝,这里实际上对innerClass类来说是进行了一次浅拷贝,而对ShallShallowCopy类来说就是一次深拷贝.


  • 总结:
    现在我们大概搞清楚了深浅拷贝之间的概念了.在JAVA语言中,实际上深浅拷贝只是相对的,如果一个对象内部只有基本数据类型,那用clone()方法获取的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那使用clone()方法就是一次浅拷贝的操作.

更新时间:
2019-5-5
17:09

你可能感兴趣的:([知识向] __ 关于深浅拷贝)