浅拷贝和深拷贝原理分析

目录
  • 前言
  • 关于引用
  • 关于浅拷贝和深拷贝
    • 浅拷贝
    • 深拷贝
  • 序列化
  • 如何选择拷贝方式
  • 其他拷贝方式

前言

因为它涉及到对象的引用关系,涉及到 Java 是传值还是传递引用关系,这通常是面试的重点。
所以在聊深拷贝和浅拷贝之前,我们先来聊一聊引用关系。

关于引用

在 Java 中,除了基本数据类型(四类八种数据类型)之外,还存在引用数据类型。
一般使用 = 号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值。
但是对于对象而言,其实赋值的只是这个对象的引用,也就是将原对象的引用传递过去。
但是他们实际上还是指向的同一个对象。

如下代码所示

public class Food{
    String name;
    int num;
    String taste;
    constructor()
       get and set()
    toString()
}

测试类:

public static void main(String[] args) {
  int i1 = 10;
  int i2 = i1; // 基本数据类型的拷贝,拷贝值
  System.out.println("i2 = " + i2);
  Food milk = new Food("milk",1,"fragrance");
  Food food = milk;
  System.out.printf("food = " + food);
  System.out.println("milk = " + milk); // milk 和 food 都指向同一个堆内存对象
}

如果用图表示的话,应该是下面这样的:

不用纠结 Java 中到底是值传递还是引用传递这种无意义的争论中

  • 对于基本数据类型,传递的是数据类型的值。
  • 对于引用类型来说,传递的是对象的引用,也就是对象的地址就可以了。

关于浅拷贝和深拷贝

浅拷贝和深拷贝其实就是在引用的这个基础上来做区分的,如果在拷贝的时候,只对基本数据类型进行拷贝,对引用数据类型只是进行了引用的传递,没有真正的创建一个新的对象,这种拷贝方式就认为是浅拷贝。
反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,这种拷贝方式就被认为是深拷贝。

浅拷贝

那么如何实现浅拷贝(Shallow copy)呢?
很简单,就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone() 方法就可以了。

下面我们对 Food 类进行修改
我们让他实现 Cloneable 接口,并重写 clone() 方法。

public class Food implements Cloneable{
    ...
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
  ...
}

然后在测试类中的代码如下

Food milk = new Food("milk",1,"fragrance");
Food food = (Food)milk.clone();
System.out.println("milk = " + milk);
System.out.println("food = " + food);

可以看到,现在的 food 对象是由 milk 对象拷贝出来的
那么此时的 food 对象和 milk 对象是同一个对象吗?
我们通过打印,可以看到这两个对象的原生 hashcode。

milk = com.cxuan.objectclone.Food@3cd1a2f1
food = com.cxuan.objectclone.Food@4d7e1886

可以发现,food 和 milk 并不是同一个对象,那 milk 中还有三个属性值
这三个属性值在 food 中是不是也一样呢?
为了验证这个猜想,我们重写了 toString 方法。

@Override
public String toString() {
  return "Food{" +
    "name='" + name + '\'' +
    ", num=" + num +
    ", taste='" + taste + '\'' +
    '}';
}

然后再次打印 food 和 milk ,可以观察到如下结果

milk = Food{name='milk', num=1, taste='fragrance'}
food = Food{name='milk', num=1, taste='fragrance'}

虽然看起来是两种完全不同的称呼!但是他们却有一种共同的能力:写作!

我们还是通过图示来说明一下:

这幅图看出门道了么?在堆区分别出现了两个 Food 对象

这同时表明 clone 方法会重新创建一个对象并为其分配一块内存区域;
虽然出现了两个对象,但是两个对象中的属性值是一样的,这也是换汤不换药,虽然汤和药是不同的东西(对象),但是他们都溶于水(属性值)。

深拷贝

虽然浅拷贝是一种换汤不换药的说法,但是在 Java 世界中还是有一种说法是,它就是我们所熟悉的深拷贝(Deep copy),
先来抛出一下深拷贝的定义:在进行对象拷贝的基础上,对对象的成员变量也依次拷贝的方式被称为深拷贝。

深拷贝原来就是在浅拷贝的基础上再复制一下它的属性值,上代码!

我们先增加一个饮品类 Drink

public class Drink implements Cloneable {
    String name;
    get and set()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    toString()
}

然后更改一下 Food 类,因为 Drink 也算是 Food ,所以我们在 Food 类中增加对 Drink 的引用,然后再修改 get set 、toString 、clone 、构造方法,修改后的 Food 类代码如下

public class Food implements Cloneable{
    String name;
    int num;
    String taste;
    Drink drink;
    public Food(String name, int num, String taste,Drink drink) {
        this.name = name;
        this.num = num;
        this.taste = taste;
        this.drink = drink;
    }
    get and set...
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Food food = (Food)super.clone();
        food.drink = (Drink) drink.clone();
        return super.clone();
    }

    @Override
    public String toString() {
        return "Food{" +
                "name='" + name + '\'' +
                ", num=" + num +
                ", taste='" + taste + '\'' +
                ", drink=" + drink +
                '}';
    }
}

可以看到最大的改变是 clone 方法,我们在 clone 方法中,实现了对 Food 对象的拷贝,同时也实现了对 Drink 对象的拷贝,这就是我们上面所说的复制对象并复制对象的成员变量。

然后我们进行一下 Deep Copy的测试:

public static void main(String[] args) throws CloneNotSupportedException {
  Drink drink = new Drink("milk");
  Food food = new Food("humberge",1,"fragrance",drink);
  Food foodClone = (Food)food.clone();
  Drink tea = new Drink("tea");
  food.setDrink(tea);
  System.out.println("food = " + food);
  System.out.println("foodClone = " + foodClone.getDrink());

}

运行完成后的输出结果如下:

food = Food{name='humberge', num=1, taste='fragrance', drink=Drink{name='tea'}}
foodClone = Drink{name='milk'}

可以看到,我们把 foodClone 拷贝出来之后,修改 food 中的 drink 变量,却不会对 foodClone 造成改变,这就说明 foodClone 已经成功实现了深拷贝。

用图示表示的话,应该是下面这样的:

这是深拷贝之后的内存分配图,现在可以看到,food 和 foodClone 完全是两个不同的对象,它们之间不存在纽带关系。

我们上面主要探讨实现对象拷贝的方式是对象实现 Cloneable 接口,并且调用重写之后的 clone 方法,在 Java 中,还有一种实现对象拷贝的方式是使用 序列化。

序列化

使用序列化的方式主要是使用 Serializable 接口,这种方式还以解决多层拷贝的问题,多层拷贝就是引用类型里面又有引用类型,层层嵌套下去。
使用 Serializable 的关键代码如下

public Person clone() {
  Person person = null;
  try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(this);
    // 将流序列化成对象
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    person = (Person) ois.readObject();
  } catch (IOException e) {
    e.printStackTrace();
  } catch (ClassNotFoundException e) {
    e.printStackTrace();
  }
  return person;
}

使用序列化可以实现深拷贝,它的原理是将二进制字节流内容写到一个文本或字节数组,然后是从这个文本或者字节数组中读取数据,原对象写入这个文本或者字节数组后再拷贝给 clone 对象,原对象的修改不会影响 clone 对象,因为 clone 对象是从文本或者字节数组中读取的。

如何选择拷贝方式

到现在我们已经把浅拷贝和深拷贝都介绍完了,那么如何选择浅拷贝和深拷贝呢?下面是几点注意事项

  • 如果对象的属性都是基本数据类型,那么可以使用浅拷贝。
  • 如果对象有引用类型,那就要基于具体的需求来选择浅拷贝还是深拷贝。
  • 如果对象嵌套层数比较多,推荐使用 Serializable 接口实现深拷贝。
  • 如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。

其他拷贝方式

除了对象的拷贝,Java 中还提供了其他的拷贝方式

比如数组的拷贝,你可以使用 Arrays.copyof 实现数组拷贝,还可以使用默认的 clone 进行拷贝,不过这两者都是浅拷贝。

public void test() {
    int[] lNumbers1 = new int[5];
    int[] rNumbers1 = Arrays.copyOf(lNumbers1, lNumbers1.length);
    int[] lNumbers2 = new int[5];
    int[] rNumbers2 = lNumbers2.clone();
}

除了基本数组数据类型之外的拷贝,还有对象的拷贝,不过用法基本是一样的。

集合也可以实现拷贝,因为集合的底层就使用的是数组,所以用法也是一样的。

一些说明

针对 Cloneable 接口,有下面三点使用说明

  • 如果类实现了 Cloneable 接口,再调用 Object 的 clone() 方法可以合法地对该类实例进行按字段复制。
  • 如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone() 方法,则会导致抛出CloneNotSupporteddException。
  • 实现此接口的类应该使用公共方法重写 Object 的clone() 方法,因为 Object 的 clone() 方法是一个受保护的方法。

到此这篇关于浅拷贝和深拷贝原理分析的文章就介绍到这了,更多相关浅拷贝和深拷贝内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript基础心法 深浅拷贝(浅拷贝和深拷贝)

    前言 说到深浅拷贝,必须先提到的是JavaScript的数据类型,之前的一篇文章JavaScript基础心法--数据类型说的很清楚了,这里就不多说了. 需要知道的就是一点:JavaScript的数据类型分为基本数据类型和引用数据类型. 对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的. 浅拷贝 浅拷贝的意思就是只复制引用,而未复制真正的值. const originArray = [1,2,3,4,5]; const originObj = {a:'a'

  • js对象浅拷贝和深拷贝详解

    本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷贝: var Chinese = { nation:'中国' } var Doctor = { career:'医生' } function extendCopy(p) { var c = {}; for (var i in p) { c[i] = p[i]; } c.uber = p; return c; } 使用的时候,这样写

  • 浅拷贝和深拷贝深入理解(shallow copy VS deep copy)

    引言C#中有两种类型变量,一种 是值类型变量,一种是引用类型变量,对于值类型变量,深拷贝和前拷贝都是通过赋值操作符号(=)实现,其效果一致,将对象中的值类型的字段拷贝到新的对象中.这个很容易理解. 本文重点讨论引用类型变量的拷贝机制和实现. C#中引用类型对象的copy操作有两种: •浅拷贝(影子克隆/shallow copy):只复制对象的值类型字段,对象的引用类型,仍属于原来的引用.•深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象.就是说完全是新对象产生的. 浅拷贝和

  • Python中的深拷贝和浅拷贝详解

    要说清楚Python中的深浅拷贝,需要搞清楚下面一系列概念: 变量-引用-对象(可变对象,不可变对象)-切片-拷贝(浅拷贝,深拷贝) [变量-对象-引用] 在Python中一切都是对象,比如说:3, 3.14, 'Hello', [1,2,3,4],{'a':1}...... 甚至连type其本身都是对象,type对象 Python中变量与C/C++/Java中不同,它是指对象的引用,Python是动态类型,程序运行时候,会根据对象的类型 来确认变量到底是什么类型. 单独赋值: 比如说: 复制代

  • 深入理解python中的浅拷贝和深拷贝

    在讲什么是深浅拷贝之前,我们先来看这样一个现象: a = ['scolia', 123, [], ] b = a[:] b[2].append(666) print a print b 为什么我只对b进行修改,却影响到了a呢?看过我在之前的文章中就说过:序列中保存的都是内存的引用. 所以,当我们通过b去修改里面的空列表的时候,其实就是修改内存中的同一个对象,所以会影响到a. a = ['scolia', 123, [], ] b = a[:] print id(a), id(a[0]), id(

  • 浅拷贝和深拷贝原理分析

    目录 前言 关于引用 关于浅拷贝和深拷贝 浅拷贝 深拷贝 序列化 如何选择拷贝方式 其他拷贝方式 前言 因为它涉及到对象的引用关系,涉及到 Java 是传值还是传递引用关系,这通常是面试的重点. 所以在聊深拷贝和浅拷贝之前,我们先来聊一聊引用关系. 关于引用 在 Java 中,除了基本数据类型(四类八种数据类型)之外,还存在引用数据类型. 一般使用 = 号做赋值操作的时候,对于基本数据类型,实际上是拷贝的它的值. 但是对于对象而言,其实赋值的只是这个对象的引用,也就是将原对象的引用传递过去. 但

  • JS浅拷贝和深拷贝原理与实现方法分析

    本文实例讲述了JS浅拷贝和深拷贝原理与实现方法.分享给大家供大家参考,具体如下: 浅拷贝只会拷贝一层,深层的引用类型改变还是会受到影响. 深拷贝是所有内部的属性还有值都被拷贝了一份,不管深层的引用类型怎么改都不会受到影响. 浅拷贝的实现方式 1.自定义函数 function shallowClone (initalObj) { var obj = {}; for ( var i in initalObj) { obj[i] = initalObj[i]; } return obj; } 2.ES

  • JavaScript对象的浅拷贝与深拷贝实例分析

    本文实例讲述了JavaScript对象的浅拷贝和深拷贝.分享给大家供大家参考,具体如下: 1.浅拷贝 仅仅复制对象的引用,而不是对象本身. var person = { name: 'Alice', friends: ['Bruce', 'Cindy'] } var student = { id: 30 } student = simpleClone(person, student); student.friends.push('David'); alert(person.friends); f

  • C++浅拷贝与深拷贝及引用计数分析

    C++浅拷贝与深拷贝及引用计数分析 在C++开发中,经常遇到的一个问题就是与指针相关的内存管理问题,稍有不慎,就会造成内存泄露.内存破坏等严重的问题.不像Java一样,没有指针这个概念,所以也就不必担心与指针相关的一系列问题,但C++不同,从C语言沿袭下来的指针是其一大特点,我们常常要使用new/delete来动态管理内存,那么问题来了,特别是伴随着C++的继承机制,如野指针.无效指针使用.内存泄露.double free.堆碎片等等,这些问题就像地雷一样,一不小心就会踩那么几颗. 先来谈一下C

  • Python中字典的浅拷贝与深拷贝用法实例分析

    本文实例讲述了Python中字典的浅拷贝与深拷贝用法.分享给大家供大家参考,具体如下: 最近发现的一个很值得记录的东西就是python字典的浅拷贝问题 首先,明确一下什么是浅拷贝,什么是深拷贝: 简单的来说就是,在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存 也就是说,在浅拷贝情况下,不同引用指向的是同一块内存,改其中一个引用,那么其他引用也会跟着改变 应用到python 的字典复制过程: # codi

  • JavaScript实现浅拷贝与深拷贝的方法分析

    本文实例讲述了JavaScript实现浅拷贝与深拷贝的方法.分享给大家供大家参考,具体如下: 平时使用数组复制时,我们大多数会使用'=',这只是浅拷贝,存在很多问题.比如 let arr = [1,2,3,4,5]; let arr2 = arr; console.log(arr) //[1, 2, 3, 4, 5] console.log(arr2) //[1, 2, 3, 4, 5] arr[0] = 6; console.log(arr) //[6, 2, 3, 4, 5] console

  • Python函数用法和底层原理分析

    目录 Python函数用法和底层分析 函数的基本概念 Python 函数的分类 核心要点 形参和实参 文档字符串(函数的注释) 返回值 函数也是对象,内存底层分析 变量的作用域(全局变量和局部变量) 部变量和全局变量效率测试 参数的传递 传递不可变对象的引用 浅拷贝和深拷贝 传递不可变对象包含的子对象是可变的情况 参数的几种类型 位置参数 默认值参数 命名参数 可变参数 强制命名参数 lambda 表达式和匿名函数 eval()函数 递归函数 Python函数用法和底层分析 函数是可重用的程序代

  • Python浅拷贝与深拷贝用法实例

    本文实例讲述了Python浅拷贝与深拷贝用法.分享给大家供大家参考.具体分析如下: >>> person=['name',['savings',100]] >>> hubby=person[:] >>> wifey=list(person) >>> [id(x) for x in person,hubby,wifey] [3074051788L, 3074061740L, 3074061996L] >>> [id(y

  • 浅析javaScript中的浅拷贝和深拷贝

    1.javaScript的变量类型 (1)基本类型: 5种基本数据类型Undefined.Null.Boolean.Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问. (2)引用类型: 存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置.当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据. JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一

  • Python中的赋值、浅拷贝、深拷贝介绍

    和很多语言一样,Python中也分为简单赋值.浅拷贝.深拷贝这几种"拷贝"方式. 在学习过程中,一开始对浅拷贝理解很模糊.不过经过一系列的实验后,我发现对这三者的概念有了进一步的了解. 一.赋值 赋值算是这三种操作中最常见的了,我们通过一些例子来分析下赋值操作: str例 复制代码 代码如下: >>> a = 'hello' >>> b = 'hello' >>> c = a >>> [id(x) for x in

随机推荐