当前位置:   article > 正文

浅拷贝和深拷贝的基本含义和应用场景_浅拷贝和深拷贝应用场景

浅拷贝和深拷贝应用场景

前言:在日常的开发中,我们常常遇到一些我们不懂的知识,比如我最近看到一个Object.assign() ,就不太清楚其究竟代表着啥意思,因而在查阅资料后,得知其是前端浅拷贝的一种方式。以前也有听说过深拷贝,但不太清楚其代表的意思。因此,今天借此篇博文,来看看什么是浅拷贝,什么又是深拷贝,它们又是如何在开发中运用的。

1,浅拷贝和深拷贝的概念

1.1 浅拷贝和深拷贝

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

为了便于对于浅拷贝和深拷贝的记忆,我们引入引用拷贝和对象拷贝来加深其理解:

1.2 引用拷贝和对象拷贝

引用拷贝:创建一个指向对象的引用变量的拷贝,具体代码如下:

  1. public class QuoteCopy {
  2. public static void main(String[] args) {
  3. Teacher teacher = new Teacher("riemann", 28);
  4. Teacher otherTeacher = teacher;
  5. System.out.println(teacher);
  6. System.out.println(otherTeacher);
  7. }
  8. }
  9. class Teacher {
  10. private String name;
  11. private int age;
  12. public Teacher(String name, int age) {
  13. this.name = name;
  14. this.age = age;
  15. }
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. }

输出结果:

  1. com.test.Teacher@28a418fc
  2. com.test.Teacher@28a418fc

结果分析:由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherTeacher的只是引用而已,他们都指向了一个相同的对象Teacher(“riemann”,28)。 这就叫做引用拷贝。

对象拷贝:创建对象本身的一个副本,代码如下:

  1. public class ObjectCopy {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Teacher teacher = new Teacher("riemann", 28);
  4. Teacher otherTeacher = (Teacher) teacher.clone();
  5. System.out.println(teacher);
  6. System.out.println(otherTeacher);
  7. }
  8. }
  9. class Teacher implements Cloneable {
  10. private String name;
  11. private int age;public Teacher(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. public String getName() {
  16. return name;
  17. }
  18. public void setName(String name) {
  19. this.name = name;
  20. }
  21. public int getAge() {
  22. return age;
  23. }
  24. public void setAge(int age) {
  25. this.age = age;
  26. }
  27. public Object clone() throws CloneNotSupportedException {
  28. Object object = super.clone();
  29. return object;
  30. }
  31. }

输出结果:

  1. com.test.Teacher@28a418fc
  2. com.test.Teacher@5305068a

结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。

1.3 浅拷贝和深拷贝的实例

深拷贝和浅拷贝都是对象拷贝,其各有特点,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象,因而相对开销较小;深拷贝把要复制的对象所引用的对象都复制了一遍,速度较慢并且花销较大。在实际的应用中,我们对于浅拷贝的方法用的较多,比如常见的clone()方法,其拷贝出来的对象是通过浅拷贝,若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝,具体代码示例如下:

我们可以通过创建一个学生对象,里边引入教授来区别浅拷贝和深拷贝:

  1. //浅拷贝内部对象——教授
  2. class Professor0 implements Cloneable {
  3. String name;
  4. int age;
  5. Professor0(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. public Object clone() throws CloneNotSupportedException {
  10. return super.clone();
  11. }
  12. }
  13. //浅拷贝克隆和修改对象——学生
  14. class Student0 implements Cloneable {
  15. String name;// 常量对象。
  16. int age;
  17. Professor0 p;// 学生1和学生2的引用值都是一样的。
  18. Student0(String name, int age, Professor0 p) {
  19. this.name = name;
  20. this.age = age;
  21. this.p = p;
  22. }
  23. public Object clone() {
  24. Student0 o = null;
  25. try {
  26. o = (Student0) super.clone();
  27. } catch (CloneNotSupportedException e) {
  28. System.out.println(e.toString());
  29. }
  30. return o;
  31. }
  32. }

通过固定方法调用查看区别:

  1. public class ShallowCopy {
  2. public static void main(String[] args) {
  3. Professor0 p = new Professor0("wangwu", 50);
  4. Student0 s1 = new Student0("zhangsan", 18, p);
  5. Student0 s2 = (Student0) s1.clone();
  6. s2.p.name = "lisi";
  7. s2.p.age = 30;
  8. s2.name = "z";
  9. s2.age = 45;
  10. System.out.println("学生s1的姓名:" + s1.name +
  11. "\n学生s1教授的姓名:" + s1.p.name + "," +
  12. "\n学生s1教授的年纪" + s1.p.age);// 学生1的教授
  13. }
  14. }
  15. 结果:s2变了,但s1也变了
  16. 证明:s1的p和s2的p指向的是同一个对象。

这在我们有的实际需求中,却不是这样,因而我们需要深拷贝:

  1. //深拷贝内部对象——教授
  2. class Professor implements Cloneable {
  3. String name;
  4. int age;
  5. Professor(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. public Object clone() {
  10. Object o = null;
  11. try {
  12. o = super.clone();
  13. } catch (CloneNotSupportedException e) {
  14. System.out.println(e.toString());
  15. }
  16. return o;
  17. }
  18. }
  19. //浅拷贝克隆对象——学生
  20. class Student implements Cloneable {
  21. String name;
  22. int age;
  23. Professor p;
  24. Student(String name, int age, Professor p) {
  25. this.name = name;
  26. this.age = age;
  27. this.p = p;
  28. }
  29. public Object clone() {
  30. Student o = null;
  31. try {
  32. o = (Student) super.clone();
  33. } catch (CloneNotSupportedException e) {
  34. System.out.println(e.toString());
  35. }
  36. o.p = (Professor) p.clone();
  37. return o;
  38. }
  39. }

深拷贝方法修改对象:

  1. public class DeepCopy {
  2. public static void main(String args[]) {
  3. long t1 = System.currentTimeMillis();
  4. Professor p = new Professor("wangwu", 50);
  5. Student s1 = new Student("zhangsan", 18, p);
  6. Student s2 = (Student) s1.clone();
  7. s2.p.name = "lisi";
  8. s2.p.age = 30;
  9. System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age);// 学生1的教授不改变。
  10. long t2 = System.currentTimeMillis();
  11. System.out.println(t2-t1);
  12. }
  13. }

当然我们还有一种深拷贝方法,就是将对象串行化:

  1. import java.io.*;
  2. class Professor2 implements Serializable {
  3. private static final long serialVersionUID = 1L;
  4. String name;
  5. int age;Professor2(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. }
  10. class Student2 implements Serializable {
  11. private static final long serialVersionUID = 1L;
  12. String name;// 常量对象。
  13. int age;
  14. Professor2 p;// 学生1和学生2的引用值都是一样的。
  15. Student2(String name, int age, Professor2 p) {
  16. this.name = name;
  17. this.age = age;
  18. this.p = p;
  19. }
  20. public Object deepClone() throws IOException, OptionalDataException,
  21. ClassNotFoundException {
  22. // 将对象写到流里
  23. ByteArrayOutputStream bo = new ByteArrayOutputStream();
  24. ObjectOutputStream oo = new ObjectOutputStream(bo);
  25. oo.writeObject(this);
  26. // 从流里读出来
  27. ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
  28. ObjectInputStream oi = new ObjectInputStream(bi);
  29. return (oi.readObject());
  30. }
  31. }
  32. public class DeepCopy2 {
  33. public static void main(String[] args) throws OptionalDataException,
  34. IOException, ClassNotFoundException {
  35. long t1 = System.currentTimeMillis();
  36. Professor2 p = new Professor2("wangwu", 50);
  37. Student2 s1 = new Student2("zhangsan", 18, p);
  38. Student2 s2 = (Student2) s1.deepClone();
  39. s2.p.name = "lisi";
  40. s2.p.age = 30;
  41. System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age); // 学生1的教授不改变。
  42. long t2 = System.currentTimeMillis();
  43. System.out.println(t2-t1);
  44. }
  45. }

参考资料:(144条消息) Java深入理解深拷贝和浅拷贝区别_老周聊架构的博客-CSDN博客_java深拷贝和浅拷贝的区别

2.浅拷贝和深拷贝的适用场景

2.1 浅拷贝和深拷贝的前端应用

浅拷贝和深拷贝都是只针对于像Object,Array这样的复杂对象,经常用于前端js对象的封装和改变。其两者的区别在于——浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制,

浅拷贝就是增加了一个指针指向已存在的内存(JavaScript并没有指针的概念,这里只是用于辅助说明),浅拷贝只是拷贝了内存地址,子类的属性指向的是父类属性的内存地址,当子类的属性修改后,父类的属性也随之被修改。就如同:一件衣服两个人穿不管你穿还是我穿都还是同一件衣服。

深拷贝就是增加一个指针,并申请一个新的内存,并且让这个新增加的指针指向这个新的内存地址使用深拷贝,在释放内存的时候就不会像浅拷贝一样出现释放同一段内存的错误,当我们需要复制原对象但有不能修改原对象的时候,深拷贝就是一个,也是唯一的选择,就如同买不同的新衣服。像es6的新增方法都是深拷贝,所以推荐使用es6语法。

2.2浅拷贝和深拷贝的使用实例

浅拷贝:

1.Object.assign:Object.assign 是 Object 的一个方法,该方法可以用于 JS 对象的合并等多个用途,其中一个用途就是可以进行浅拷贝。该方法的第一个参数是拷贝的目标对象,后面的参数是拷贝的来源对象(也可以是多个来源)。

2、扩展运算符:使用扩展运算符也可以完成浅拷贝

3、Array.prototype.concat:数组的 concat 方法其实也是浅拷贝,使用场景比较少,使用concat连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组

4、Array.prototype.slice

数组的 slice 方法其实也是浅拷贝,使用场景比较少,同cancat

5、使用第三方库&手动实现

社区存在一些优秀工具函数库,在开发过程中可以直接利用这些库暴露的函数直接实现浅拷贝,比如lodash就提供了clone方法供用户进行浅拷贝

深拷贝:

1.可以通过 for in 实现。

  1. function deepCopy1(obj) {
  2. let o = {}
  3. for(let key in obj) {
  4. o[key] = obj[key]
  5. }
  6. return o
  7. }
  8. let obj = {
  9. a:1,
  10. b: undefined,
  11. c:function() {},
  12. }
  13. console.log(deepCopy1(obj))

2. 可以借用JSON对象的parse和stringify(对象的深拷贝)

  1. function deepClone(obj){
  2.     let _obj = JSON.stringify(obj),
  3.         objClone = JSON.parse(_obj);
  4.     return objClone
  5. }    
  6. let a=[0,1,[2,3],4],
  7.     b=deepClone(a);
  8. a[0]=1;
  9. a[2][0]=1;
  10. console.log(a,b);

3.递归 自身调用自身(对象的深拷贝)

  1. function deepClone1(obj) {
  2.   //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
  3.   var objClone = Array.isArray(obj) ? [] : {};
  4.   //进行深拷贝的不能为空,并且是对象或者是
  5.   if (obj && typeof obj === "object") {
  6.     for (key in obj) {
  7.       if (obj.hasOwnProperty(key)) {
  8.         if (obj[key] && typeof obj[key] === "object") {
  9.           objClone[key] = deepClone1(obj[key]);
  10.         } else {
  11.           objClone[key] = obj[key];
  12.         }
  13.       }
  14.     }
  15.   }
  16.   return objClone;
  17. }


4.concat(数组的深拷贝)

使用concat合并数组,会返回一个新的数组。

5.有些同学可能见过用系统自带的JSON来做深拷贝的例子,下面来看下代码实现

  1. function cloneJSON(source) {
  2. return JSON.parse(JSON.stringify(source));
  3. }

 2.3 浅拷贝与深拷贝的关系(待更新)

浅拷贝与深拷贝是一道经久不衰的面试题。

我们要答好这道题,就必须先理清其关系,简单来说,深拷贝可以视为浅拷贝+递归。

深拷贝、浅拷贝的理解与使用场景 - 简书 (jianshu.com)

https://blog.csdn.net/weixin_50964668/article/details/113922558


 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/68377
推荐阅读
相关标签
  

闽ICP备14008679号