java赋值、浅拷贝和深拷贝学习笔记

java赋值、浅拷贝和深拷贝

链接: 继续上一篇对象赋值的文章的深入学习.

值类型vs引用类型

这两个概念的区分对我们问题的研究很重要

  1. 在Java中,像数组、类Class、枚举Enum、Integer包装类等等,就是典型的引用类型,所以操作时一般来说采用的也是引用传递的方式
  2. Java的语言级基础数据类型,诸如int这些基本类型,操作时一般采取的则是值传递的方式,称它为值类型。

赋值

先定义两个类

public class Student{
    private int age;
    private String name;
    private Major major;//学生选课
    
    //其他代码
public class Major{
    private String majorName;//选修课名称
    private int majorId;//选修课id
    
    //其他代码
}

测试赋值类

    public static void main(String[] args) {
        //值类型
        String s1="hello";
        String s2=s1;//值类型赋值,s1、s2为两个数据
        s2="world";
        System.out.println(s1);
        System.out.println(s2);

        //引用类型
        Major m=new Major("java语言基础",123456789);
        Student stu1=new Student(18,"张三",m);

        Student stu2=stu1;//对象赋值,引用关系,并没有生成新的实际对象
        System.out.println(stu1==stu2);
        System.out.println(stu1);
        System.out.println(stu2);

        stu1.setAge(35);
        Major m2=new Major("java语言基础",10000000);
        stu2.setMajor(m2);
        System.out.println(stu1==stu2);
        System.out.println(stu2);
        System.out.println(stu1);
    }

输出的结果:

hello
world

true
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}

true
Student{age=35, name='张三', major=Major{majorName='java语言基础', majorId=10000000}}
Student{age=35, name='张三', major=Major{majorName='java语言基础', majorId=10000000}}

可见,对象赋值(引用类型)后没有生成新的对象,二者的对象地址是一样的
即是上一篇文章我遇到的问题

浅拷贝

介绍:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

特点:

  1. 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
  2. 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

结构图:
在这里插入图片描述

深拷贝

介绍:
浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 stu2 的 Major,但是 stu1 的 Major 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。
在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

特点:

  1. 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
  2. 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
  3. 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
  4. 深拷贝相比于浅拷贝速度较慢并且花销较大。

结构图:
在这里插入图片描述

浅拷贝的实现

以上面的例子Student学生类实现
浅拷贝的典型实现方式是:实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。。

public class Student implements Cloneable{
    private int age;
    private String name;
    private Major major;//学生选课

    //浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    //其他代码
}

测试类:

public class Test02 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Major m=new Major("java语言基础",123456789);
        Student stu1=new Student(18,"张三",m);

        //stu1拷贝得到stu2
        Student stu2= (Student) stu1.clone();

        System.out.println(stu1==stu2);
        System.out.println(stu1);
        System.out.println(stu2);
        System.out.println();

        stu2.setAge(13);
        m.setMajorId(1000000000);
        //Major m2=new Major("java语言基础",100000000);
        //stu1.setMajor(m2);
        System.out.println(stu1);
        System.out.println(stu2);
    }
}

输出的结果:

false
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}

Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}
Student{age=13, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}

由输出的结果可见,通过 stu1.clone() 拷贝对象后得到的 stu2,和 stu1 是两个不同的对象。stu1和 stu2 的基础数据类型的修改互不影响,而引用类型 Major 修改后是会有影响的。

深拷贝的实现

虽然clone()方法可以完成对象的拷贝工作,但是注意:clone()方法默认是浅拷贝行为,就像上面的例子一样。若想实现深拷贝需覆写 clone()方法实现引用对象的深度遍历式拷贝,进行地毯式搜索。
所以对于上面的例子,如果想实现深拷贝,首先需要对更深一层次的引用类Major做改造,让其也实现Cloneable接口并重写clone()方法:

public class Major implements Cloneable{
    private String majorName;//选修课名称
    private int majorId;//选修课id
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    //其他代码
}

其次我们还需要在顶层的调用类中重写clone方法,来调用引用类型字段的clone()方法实现深度拷贝,对应到本文那就是Student类:

public class Student implements Cloneable{
    private int age;
    private String name;
    private Major major;//学生选课

    //深拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student=(Student) super.clone();
        student.major=(Major) major.clone();  //重要
        return student;
    }
    //其他代码
}

这时候我们还是用上面的测试例子
此时输出结果为:

false
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}
Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}

Student{age=18, name='张三', major=Major{majorName='java语言基础', majorId=1000000000}}
Student{age=13, name='张三', major=Major{majorName='java语言基础', majorId=123456789}}

很明显,这时候stu1和stu2两个对象就完全独立了,不受互相的干扰。

学习文章

https://www.jianshu.com/p/94dbef2de298
github.com/hansonwang99/JavaCollection

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值