iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >浅拷贝和深拷贝原理分析
  • 849
分享到

浅拷贝和深拷贝原理分析

2024-04-02 19:04:59 849人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录前言关于引用关于浅拷贝和深拷贝浅拷贝深拷贝序列化如何选择拷贝方式其他拷贝方式前言 因为它涉及到对象的引用关系,涉及到 Java 是传值还是传递引用关系,这通常是面试的重点。 所以

前言

因为它涉及到对象的引用关系,涉及到 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 都指向同一个堆内存对象
}

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

image-20210807143403402

不用纠结 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'}

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

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

image-20210817000226163

这幅图看出门道了么?在堆区分别出现了两个 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 已经成功实现了深拷贝。

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

image-20210807170417799

这是深拷贝之后的内存分配图,现在可以看到,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() 方法是一个受保护的方法。

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

--结束END--

本文标题: 浅拷贝和深拷贝原理分析

本文链接: https://www.lsjlt.com/news/133189.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 浅拷贝和深拷贝原理分析
    目录前言关于引用关于浅拷贝和深拷贝浅拷贝深拷贝序列化如何选择拷贝方式其他拷贝方式前言 因为它涉及到对象的引用关系,涉及到 Java 是传值还是传递引用关系,这通常是面试的重点。 所以...
    99+
    2022-11-12
  • 分析JavaScript浅拷贝和深拷贝
    本篇内容主要讲解“分析JavaScript浅拷贝和深拷贝”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“分析JavaScript浅拷贝和深拷贝”吧!一、直接赋值对象是引用类型,如果直接赋值给另外一...
    99+
    2023-06-25
  • 浅拷贝&深拷贝
    浅拷贝新的对象指向原来对象的地址 深拷贝新的对象中,原来是可变对象,会新复制一份值指向新的地址[11,22,33]若原来的对象里含有可变对象,里面的这个可变对象也会指向新的地址['qwer', 123, [44,55]] 参考:https...
    99+
    2023-01-30
  • python浅拷贝和深拷贝
    python中的赋值是按引用来传递的,如果不是赋值而是拷贝,那就需要用到copy模块了,这就不得不谈浅拷贝和深拷贝了。   浅拷贝copy()   #!/usr/bin/python  import copy  class MyClass:...
    99+
    2023-01-31
    和深 python
  • python深拷贝和浅拷贝之简单分析
    title: python 深拷贝和浅拷贝 tags: python,copy,deepcopy grammar_cjkRuby: true --- python 深拷贝和浅拷贝 python的变量的赋值都是引用 把一个变量赋值给一个...
    99+
    2023-01-30
    简单 python
  • JavaScript中的浅拷贝和深拷贝原理与实现浅析
    目录前言什么是浅拷贝实现浅拷贝什么是深拷贝实现深拷贝前言 JavaScript 中的浅拷贝和深拷贝指的是在复制对象(包括对象、数组等)时,是否只复制对象的引用地址或者在复制时创建一个...
    99+
    2023-05-17
    JavaScript深拷贝与浅拷贝 JS深拷贝与浅拷贝
  • 浅拷贝与深拷贝
       名词解释 1.对象:被分配的一块内存,存储其所代表的值 2.引用:是自动形成的从变量到对象的指针 3.注意:类型(int类型,long类型(python3已去除long类型,只剩下int类型的数据))属于对象,不是变量 4.不可变...
    99+
    2023-01-30
  • python深拷贝浅拷贝
    python深拷贝和浅拷贝问题:   什么是深拷贝?     (个人理解)深拷贝就是将原有的数据一模一样的拷贝一份,然后存到另一个地址中,而不是引用地址   什么是浅拷贝?     (个人理解)就是引用地址 (1)用等于号的拷贝都属于浅拷...
    99+
    2023-01-30
    python
  • 浅谈JavaScript浅拷贝和深拷贝
    目录一、直接赋值二、浅拷贝三、深拷贝1. JSON对象的方式2. 递归复制网上关于这个话题,讨论有很多了,根据各路情况我自己整理了一下,最后还是能接近完美的实现深拷贝,欢迎大家讨论。...
    99+
    2022-11-12
  • JavaScript深拷贝与浅拷贝原理深入探究
    目录一、JS中数据的存储形式-堆栈二、深浅拷贝的三种方式遍历赋值Object.create()遍历赋值实现深拷贝一、JS中数据的存储形式-堆栈 我们先简单理解一下堆栈分别是啥: 什么...
    99+
    2022-11-13
    JS 深拷贝 浅拷贝 JS 深拷贝 JS 浅拷贝
  • python深拷贝与浅拷贝
    可变对象与不可变对象 要理解深拷贝和浅拷贝,首先要理解可变对象和不可变对象。 不可变对象:该对象所指向的内存中的值不能被改变,修改对象的值时,由于其指向的值不能被改变,因此实际上是在内存中重新开辟一个地址用来存储新的值,然后将对象指向这个...
    99+
    2023-01-30
    python
  • 怎么理解python指针拷贝,浅拷贝和深拷贝
    本篇内容主要讲解“怎么理解python指针拷贝,浅拷贝和深拷贝”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解python指针拷贝,浅拷贝和深拷贝”吧!首先对于不可变类型int,strin...
    99+
    2023-06-02
  • javascript中怎么区分浅拷贝和深拷贝并实现深拷贝
    这篇文章将为大家详细讲解有关javascript中怎么区分浅拷贝和深拷贝并实现深拷贝,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。什么是拷贝 一个东西的拷贝看起来像是原来...
    99+
    2022-10-19
  • 什么是深拷贝和浅拷贝
    深拷贝和浅拷贝分别是指:深拷贝是指拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉。浅拷贝是指对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。...
    99+
    2023-10-29
  • 浅拷贝和深拷贝的区别
    浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。 2、深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任...
    99+
    2023-09-21
    python
  • java对象拷贝之深拷贝与浅拷贝
    要实现对象拷贝必须实现一个Cloneable接口,如果不实现这个接口就会产生一个CloneNotSupportedException异常。其实这个接口一个方法都没有,因此这类接口常被称作标记接口。Object 中有一个clone() 方法实...
    99+
    2016-12-31
    java入门 java 对象 深拷贝 浅拷贝
  • C++简明图解分析浅拷贝与深拷贝
    目录浅拷贝(单纯值拷贝)深拷贝总结拷贝构造函数的调用时机类中有指针成员 才会讨论 浅拷贝 和深拷贝问题。 浅拷贝(单纯值拷贝) #include <iostream> ...
    99+
    2022-11-13
  • python之浅拷贝与深拷贝
    浅拷贝是对于一个对象的顶层拷贝通俗的理解是:拷贝了引用,并没有拷贝内容 In [10]: a = [11,22,33] In [11]: b = a In [12]: id(a) Out[12]: 140343572333832 I...
    99+
    2023-01-31
    python
  • Python中的深拷贝浅拷贝
    什么是浅拷贝,什么是深拷贝? 一、浅拷贝 浅拷贝就是:拷贝了引用,并没有拷贝内容,和在Linux中的硬链接有些类似,在python中,使用=赋值的动作就是一个浅拷贝 浅拷贝中的新旧对象,共用一个内存地...
    99+
    2023-09-08
    python 开发语言
  • 深入理解python中的浅拷贝和深拷贝
    在讲什么是深浅拷贝之前,我们先来看这样一个现象: a = ['scolia', 123, [], ] b = a[:] b[2].append(666) print a print b 为什么我只对...
    99+
    2022-06-04
    和深 python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作