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

前言:在日常的开发中,我们常常遇到一些我们不懂的知识,比如我最近看到一个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 引用拷贝和对象拷贝

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

public class QuoteCopy {
    public static void main(String[] args) {
        Teacher teacher = new Teacher("riemann", 28);
        Teacher otherTeacher = teacher;
        System.out.println(teacher);
        System.out.println(otherTeacher);
    }
}

class Teacher {
    private String name;
    private int age;
    public Teacher(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

输出结果:

com.test.Teacher@28a418fc
com.test.Teacher@28a418fc

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

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

public class ObjectCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("riemann", 28);
        Teacher otherTeacher = (Teacher) teacher.clone();
        System.out.println(teacher);
        System.out.println(otherTeacher);
    }
}

class Teacher implements Cloneable {
    private String name;
    private int age;public Teacher(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Object clone() throws CloneNotSupportedException {
        Object object = super.clone();
        return object;
    }
}

输出结果:

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

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

1.3 浅拷贝和深拷贝的实例

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

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

//浅拷贝内部对象——教授
class Professor0 implements Cloneable {
        String name;
        int age;

    Professor0(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//浅拷贝克隆和修改对象——学生
class Student0 implements Cloneable {
        String name;// 常量对象。
        int age;
        Professor0 p;// 学生1和学生2的引用值都是一样的。
        Student0(String name, int age, Professor0 p) {
        this.name = name;
        this.age = age;
        this.p = p;
    }

    public Object clone() {
        Student0 o = null;
        try {
            o = (Student0) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }

        return o;
    }
}

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

public class ShallowCopy {
    public static void main(String[] args) {
        Professor0 p = new Professor0("wangwu", 50);
        Student0 s1 = new Student0("zhangsan", 18, p);
        Student0 s2 = (Student0) s1.clone();
        s2.p.name = "lisi";
        s2.p.age = 30;
        s2.name = "z";
        s2.age = 45;
        System.out.println("学生s1的姓名:" + s1.name +
        "\n学生s1教授的姓名:" + s1.p.name + "," + 
        "\n学生s1教授的年纪" + s1.p.age);// 学生1的教授
    }
}

结果:s2变了,但s1也变了
证明:s1的p和s2的p指向的是同一个对象。

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

//深拷贝内部对象——教授
class Professor implements Cloneable {
    String name;
    int age;

    Professor(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Object clone() {
        Object o = null;
        try {
            o = super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        return o;
    }
}
//浅拷贝克隆对象——学生
class Student implements Cloneable {
    String name;
    int age;
    Professor p;

    Student(String name, int age, Professor p) {
        this.name = name;
        this.age = age;
        this.p = p;
    }

    public Object clone() {
        Student o = null;
        try {
            o = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        o.p = (Professor) p.clone();
        return o;
    }
}

深拷贝方法修改对象:

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

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

import java.io.*;

class Professor2 implements Serializable {

    private static final long serialVersionUID = 1L;
    String name;
    int age;Professor2(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

class Student2 implements Serializable {
      private static final long serialVersionUID = 1L;
      String name;// 常量对象。
      int age;
      Professor2 p;// 学生1和学生2的引用值都是一样的。
      Student2(String name, int age, Professor2 p) {
           this.name = name;
           this.age = age;
           this.p = p;
       }

    public Object deepClone() throws IOException, OptionalDataException,
            ClassNotFoundException {
        // 将对象写到流里
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(this);
        // 从流里读出来
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return (oi.readObject());
    }
}

public class DeepCopy2 {
    public static void main(String[] args) throws OptionalDataException,
            IOException, ClassNotFoundException {
        long t1 = System.currentTimeMillis();
        Professor2 p = new Professor2("wangwu", 50);
        Student2 s1 = new Student2("zhangsan", 18, p);
        Student2 s2 = (Student2) s1.deepClone();
        s2.p.name = "lisi";
        s2.p.age = 30;
        System.out.println("name=" + s1.p.name + "," + "age=" + s1.p.age); // 学生1的教授不改变。
        long t2 = System.currentTimeMillis();
        System.out.println(t2-t1);
    }
}

参考资料:(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 实现。

function deepCopy1(obj) {
  let o = {}
  for(let key in obj) {
    o[key] = obj[key]
  }
  return o
}
 
let obj = {
  a:1,
  b: undefined,
  c:function() {},
}
console.log(deepCopy1(obj))

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

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

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

function deepClone1(obj) {
  //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
  var objClone = Array.isArray(obj) ? [] : {};
  //进行深拷贝的不能为空,并且是对象或者是
  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (obj[key] && typeof obj[key] === "object") {
          objClone[key] = deepClone1(obj[key]);
        } else {
          objClone[key] = obj[key];
        }
      }
    }
  }
  return objClone;
}


4.concat(数组的深拷贝)

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

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

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

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

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

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

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

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


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值