Java学习笔记:2022年1月9日(其一)

摘要:这篇笔记主要记录了Java运行时中的两种变量、以及参数的两种传递方式。

1.不同变量的详细探讨

1.Java中的两种变量

​ 之前我们讨论了Java运行时中的栈区的运行机制,也就是在Java中,各种方法执行的过程,关于方法中的一些变量行为,我们也进行了简要的探讨,但是关于变量的存储以及方法的参数传递,我们并没有进行深刻的讨论,因此留下了一些遗留问题,在1月9日的笔记中,我们将对这些遗留问题进行讨论,首先我们要讨论的是Java中的两种变量。

​ 在Java中有着8中基本变量,但是我们所说的两种变量并不是8种基本变量中的两种,而是更加广义的两种变量,即引用类型变量和基本类型变量。这两种类型的变量的差异在哪里呢?接下来我们将进行深刻的讨论。

基本类型变量:基本类型变量所占用的内存空间是一定的,这是它和引用类型变量的最大区别,不同类型的引用类型的变量所占用的内存空间可能不同,但是只要两个基本类型的变量类型相同,即使二者存的数据不同,他们的所占用的内存空间也是等长的。这个特性导致基本类型的变量被操作起来非常的方便,这里的操作不是指被人操作,而是被计算机操作,只要变量类型被定义,计算机就可以知道它在内存中占用的空间大小,进而使用相应的长度对其进行存取,如基本类型的数组中,就可以很快的进行取值,因为每个元素的长度固定,根据一个公式就可以知道指定位置的元素的首地址。

引用类型变量:引用类型变量所占用的内存空间是随机大小的,Java中的数组就是引用类型,同时各种类的对象,也都是引用类型,因为这些个体都是需要以单个的独立个体的形式出现,同时它们的大小又不是固定的,如数组的大小就和它的长度有关系,而不同类的对象,根据它们自身内部的构造有直接关系,因此这些个体控制起来非常的麻烦,我们难以为它们在内存中分配一个固定的空间,因为当变量发生值变更时,如一个数组类型变量a要被扩容时,如果在其当前地址上进行扩容,很有可能会导致它占用后面的地址影像到其他的东西。因此为了解决这个问题,Java中的引用类型变量的值并不是真的是被赋予的实体值,而是一个地址,这个地址指向实体值。

2.堆区

​ 在Java运行时中,一共有三个区域,方法区主要是存储类信息以及静态成员,栈区是运行区,而还剩下一个区域就是堆区,堆区实际上就是一个内存分配区,引用类型变量的内存空间都在这里边,也就是说,引用类型变量的真实值的内存,都在堆区被赋予,他们都被暂存在堆区之中。

3.变量的句柄

​ 句柄是什么,句柄实际上就是变量名,在写程序时,我们想要调用变量一般都会使用变量名来直接调用,在计算机的存储中,变量名就是句柄,我们生命变量的过程就是让计算机知道句柄和某一个值得对应关系的过程,当我们使用一个变量时,编译器就可以寻找句柄对应的值并进行替换。我们可以简单理解为,在变量声明以及赋值时,在等于号左边的,就是句柄,在等于号右边的,就是值。

4.句柄和变量

​ 句柄可以理解为变量的变量名在计算机中的叫法,有了句柄,就可以让程序员使用变量名来取得某个数值,那么这个对应关系是如何产生的呢?变量的生存,是在栈区的,一个变量只有在运行起来时才会有意义,因此句柄的存放实际上是在栈区的,对于一个基本类型变量,其句柄和其真实值,是存放在一起的,也就是说在栈区参与方法运行的时候,一个基本类型变量的句柄和其真实值都被存放在栈区的内存上,而且它们是相邻的,因此系统很容易找到基本类型变量句柄对应的值,根据句柄,往后一找便是。然而引用类型变量的真实值没有和其句柄在一起,它们被存放在堆区内存上,引用类型的句柄后边实际上是地址信息,引用类型的变量句柄确实在栈区,但是它会像一个指针那样,指向堆区内存中的一个地址,在这个地址上,存放的才是它的真实值。

​ 因此我们可以知道,基本类型变量的句柄和真实值在一起,都被存放在栈区;引用类型变量的句柄没有和真实值在一起,引用类型变量是一个类似指针的存在,它存放的是它真实值的地址信息,当我们为其赋予新值的时候,实际上是在修改它的指向,当一个堆内存中的真实值没有被句柄指向时,会被视为垃圾数据,Java有一个自动回收机制,会专门回收这种没有被指向的数据。

5.引用地址

​ 在Java运行时的栈空间上,句柄们也有自己的地址,这个地址被称为引用地址,这个引用地址实际上和引用类型没有关系,只不过是这么叫而已,这里需要注意。程序的运行离不来内存,因此也离不开地址,在栈空间中的变量地址的名字被称为引用地址。因此,对于基本类型变量来说,它的值就在自己变量的引用地址上,而引用类型变量则没有,它们的值并没有位于栈区,而是位于堆内存上边的一个地址上,因此它们真实值的地址和变量所在的地址不一样。

6.小结

​ 基本类型变量的句柄和真实值被存储在一起,二者在栈区紧挨着。引用类型变量的句柄和真实值没有在一起,它的句柄在栈空间,而真实值在堆空间中,它的句柄之后存储的是一个地址信息,这个地址信息指向堆内存中的真实值,引用类型变量类似一个指针,指向自己的真实值,而不像基本类型变量那样直接存储自己的真实值。

2.Java中的传参

​ 对于传参,主要有两种类型的传参:值传递和引用传递。在Java中,不存在引用传递,同时,引用传递和上面讲到的引用地址有着紧密的联系,之后会详细解释。

​ 很多人都会说:在Java中不存在真正意义上的引用传递,在Java中只存在值传递,但是不知道具体是什么意思,现在我从Java底层来详细的阐述引用传递和值传递的含义。

1.值传递

​ 值传递实际上是字面意思,所谓值传递,就是传递值,就是将实参的值传递给形参,让形参复制一份使用。在Java中的值传递有些复杂,分为两个情况,下面我们使用图来解说Java中值传递的两种情况:

​ 如图所示,在栈区我们定义了两个变量,其中a变量是一个基本类型的变量,b变量是一个引用类型的变量,这两个变量被定义在栈区,拥有各自的地址,系统便是使用他们的地址进行使用的。当我们使用传参时,如果是传输a变量,那么,Java运行时会将实参a的值,也就是数字10,复制到形参的值这里去,然后基本类型变量a的值传递就完成了。在这个过程中,发生传送情况的,是a变量的值,也就是数字10,仅有这个值被传递了,因此我们称之为值传递。

​ 当我们定义了一个方法,要对b进行传参呢?这时形参发生了什么样的行为?答案是,形参获得的东西,也将是b的值,b的值是什么呢?就是堆区的,b指向的数组。那么我们如何将这个值给到形参呢?我们将b指向的数组地址复制给形参,这样就完成了参数的传递。因此,对于引用类型的变量,我们同样是使用值传递,在Java中的引用类型的传参,也同样是将变量的值给到形参,只不过是这个值实际上是值的地址,可以理解为,这个值的地址,就可以代表整个值了。

​ 而实际上,引用类型变量并不是抽象意义上的指向它的实体值,它其实也是一个固定长度的组件,只不过在栈内存中,它后边紧跟的值不是它的真实值,而是堆内存中的地址,也就是说,b实际上在堆内存中不仅仅有句柄,它也像a那样存在一个值,只不过这个值等于的是654这个真实值地址,Java虚拟机在运行时中,可以根据b的类型进行地址映射,进而在编译时将b解释为地址映射的地址处的信息,而非仅仅是这个地址值,因此在Java中,对于引用类型变量,其实传递的也是一个值,只不过传递的是实参指向的信息的地址。因此,在Java中,仅存在值传递,而非存在引用传递。

2.引用传递

​ 引用传递也是顾名思义,在上文中,我们介绍了一种地址叫做引用地址,这引用地址是变量的地址,什么是变量的地址呢?使用上图就能很轻松的理解,在栈内存上的存储变量的地址便是引用地址,如这个图中的101,102都是引用地址,引用地址是存在于栈内存中的变量地址,和堆内存中的值地址完全不同。位于栈内存上的变量自己的地址,叫做引用地址,而引用类型变量的值,通常是堆内存中有真实值信息的值地址,二者完全不是一个概念。直接传递引用地址的传参行为,被称为引用传递,在Java中,不存在引用传递。在C语言以及C++中存在引用传递,C语言的指针类型传参就是引用传递,在C语言中,存在指针类型变量,有了指针类型的变量,我们便可以直接对地址进行操作,使用&取地址的符号,我们可以直接取到变量自身的地址,在图中表现为101和102这两个地址,使用这两个地址,我们便掌握了这个变量的一切,在C语言的指针类型传参中,我们会将一个变量的引用地址传送给指针类型变量,指针类型变量便会存储这个地址,使用*符号,就可以直接对这个地址上的值进行修改,这里,形参是引用了实参的地址,因此叫引用传递。

3.总结

​ 引用传递似乎和Java中的引用类型的传参非常相似,但实际上并不是,指针和Java中的引用类型确实非常一样,因为他们存的都是一个地址,但是,在使用层面,二者大相径庭,引用类型只能指向类,或者数组类型的值,指针可以指向一切值;在C语言中,指针可以获取一个变量的地址,但是在Java中,一个引用类型变量永远得不到栈中的变量地址,它获得的永远是一个堆内存中的值地址;C语言中指针改变指向后,变量仍然具有自己的句柄,仍然存在,Java中,引用类型的变量的句柄本身,就指向它的值,当改变指向,原值就会失去句柄,进而被运行时回收。总体上来讲,C语言和Java的机制完全不同,由于Java自身定义的变量存储机制,导致在Java中,不存在引用传递,仅存在值传递。

​ 实际上,Java旨在被设计成一种绝对安全的语言,C语言中的引用传递并不是一个安全行为,尽管它非常的方便,但是引用传递的滥用会导致一个对象的封装性遭到破坏,方法的使用会导致某些相对独立的类中的属性发生改变,进而破坏某个类的独立性,因此在Java中去掉了引用传递的支持,但是由于Java中引用类型变量的设计,导致在方法中仍然有机会直接操纵值,因此弥补了这个缺陷,使用方法仍然可以改变实参的值。

值传递是传递值,引用传递是传递引用地址。在Java中引用类型变量的值传递仍然是传递值,传递的是它指向的值的对象的值,因此尽管还是传递了一个地址,但是和C语言中的传递引用地址完全不同,它传递的是堆内存中的值地址,不是传递的栈内存中的引用地址。归根结底,C语言和Java的内部机制不同,Java的值有时不和句柄相邻,存在于一个第三方位置,C语言的变量,则统统和自己的值相邻。Java中的引用类型变量的实现在底层实际上就是使用了C++的指针,但是它在Java自身体系中,被加入了一些限制,同时进行了封装,因此它和指针有些区别,引用类型变量不是指针,使用引用类型变量进行传参也不被称为引用传递。我们可以将引用类型理解为:一种被特化的,封装的,退化的指针,具有部分指针的性质,但是和C语言中的指针不太一样。

​ 在此有一个实验,就是传入两个引用类型的变量,然后对他们进行交换。结果是实参的指向不会被改变,原因就是引用类型仍然是值传递,它们在传参时将地址赋予给形参,形参在交换时使用的是等于号赋值,等于号是浅拷贝,因此只是改变了自身的指向,而形参的指向改变并不会影响外部实参的指向,因此实参的指向没有改变。这个实验现在我看来其实是理所应当的,因为我已经从根本上明白了Java中的值传递方式,然而有些学过C语言的人会认为引用类型的传参就是和C语言中的指针传参一样,它们传进来的也是引用地址,形参指向的改变就会影响外部,这是不对的,因此一定要注意。

​ 最后一点:Java中的引用类型的值传递,在进行值拷贝时,是浅拷贝,是使用等于号的浅拷贝,而不是深拷贝,如果是深拷贝,那么它的值传递将完全和基本类型的值传递相同,形参拥有了一个位于不同地址的,完全和原值一样的,但是实际上不是同一个的值,这是改变形参的值将不会影响到实参的值,使用浅拷贝的方式只是获得地址,这样形参的指向是和实参一致的,这样一来在方法中修改形参的值是可以影响到实参的,我们有时会需要使用这个特性,因此在引用类型进行值传递时,使用的是浅拷贝,只拷贝了地址。

Java学习笔记:2022年1月9日(其一)的更多相关文章

  1. 路冉的JavaScript学习笔记-2015年1月23日

    1.JavaScript的数据类型 A.原始类型:包含数值.字符串.布尔值.空值(null)和未定义值(undefined). Js原始类型均为不可改变类型.对不可变类型调用任何自带方法都不会改变原始 ...

  2. 路冉的JavaScript学习笔记-2015年2月5日

    1.为Js原始值创建临时对象,并进行属性引用 var s="text"; s.len=4;//这里Js调用new String(s)的方法创建了一个临时对象,用来属性引用 cons ...

  3. 《Java学习笔记(第8版)》学习指导

    <Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...

  4. 20145330第九周《Java学习笔记》

    20145330第九周<Java学习笔记> 第十六章 整合数据库 JDBC入门 数据库本身是个独立运行的应用程序 撰写应用程序是利用通信协议对数据库进行指令交换,以进行数据的增删查找 JD ...

  5. 20145330第七周《Java学习笔记》

    20145330第七周<Java学习笔记> 第十三章 时间与日期 认识时间与日期 时间的度量 GMT(格林威治标准时间):现在不是标准时间 世界时(UT):1972年UTC出来之前,UT等 ...

  6. java学习笔记07--日期操作类

    java学习笔记07--日期操作类   一.Date类 在java.util包中定义了Date类,Date类本身使用非常简单,直接输出其实例化对象即可. public class T { public ...

  7. java学习笔记之日期日历类

    java学习笔记之日期日历 Date日期类概述: 表示特定的瞬间,精确到毫秒 Date类的构造方法: 1.空参数构造方法 Date date = new Date(); 获取到当前操作系统中的时间和日 ...

  8. 20155234 2610-2017-2第九周《Java学习笔记》学习总结

    20155234第九周<Java学习笔记>学习总结 教材学习内容总结 数据库本身是个独立运行的应用程序 撰写应用程序是利用通信协议对数据库进行指令交换,以进行数据的增删查找 JDBC(Ja ...

  9. 20145230《java学习笔记》第七周学习总结

    20145230 <Java程序设计>第7周学习总结 教材学习内容 Lambda语法概览 我们在许多地方都会有按字符串长度排序的需求,如果在同一个方法内,我们可以使用一个byName局部变 ...

  10. Java学习笔记之---API的应用

    Java学习笔记之---API的应用 (一)Object类 java.lang.Object 类 Object 是类层次结构的根类.每个类都使用 Object 作为超类.所有对象(包括数组)都实现这个 ...

随机推荐

  1. golang中的errgroup

    0.1.索引 https://waterflow.link/articles/1665239900004 1.串行执行 假如我们需要查询一个课件列表,其中有课件的信息,还有课件创建者的信息,和课件的缩 ...

  2. 第三方代开的微信小程序更换管理员

    (1) 由于第三方代开小程序默认管理员是法人.首先使用法人微信搜索"小程序助手"小程序 (2)点击进入"小程序助手",即可看到自己企业名下未更换管理员的小程序 ...

  3. 基于mnist的P-R曲线(准确率,召回率)

    一.准确率,召回率 TP(True Positive):正确的正例,一个实例是正类并且也被判定成正类 FN(False Negative):错误的反例,漏报,本为正类但判定为假类 FP(False P ...

  4. certutil做哈希校验并下载网络文件

    微软Win系统自带,不需要安装的工具,但它是CMD命令行工具,关于命令行工具的说明和使用请参考我以前的文章 Windows系统的命令行(CLI)介绍及入门使用说明 . 这个微软自带的命令行工具叫做 c ...

  5. Linux之Docker-01

    一.镜像基础命令 1.docker version  [root@DY-Ubuntu-01 ~]#docker version               #查看 Docker 版本 2.docker ...

  6. Pycharm自定义实时模板

    pycharm添加模板 添加装饰器模板 # 1.file-->Setting-->Editor-->Code Style -->Live Templates# 2." ...

  7. Spring Cloud Alibaba 从入门到精通(2023)

    Alibaba Cloud 简介 Spring Cloud Alibaba 即 Alibaba Cloud ,基于 Spring Cloud 构建,同时封装了阿里巴巴的 Nacos.Sentinel ...

  8. SQLi

    点进去发现是个空白网页,查看源码发现一个login.php的文件,话不多说,直接选择复制然后访问 Url: http://5865f5830d034083b9bbc0dafc6b60a5d5d2309 ...

  9. python3获取列表逆序的五种方式

    前言 我们将这几种方式分为两类,一种是对列表本身进行操作,改变对应内存中的值,另一种是带有返回值的,相当于拷贝了一份 对列表内存中进行操作 sort() 函数 a = [1,2,3,4] a.sort ...

  10. java.util.Date和java.util.Calendar

    Date date = new Date();//分配初始化一个Date()对象 Calendar cal = Calendar.getInstance();//获取一个基于当前时间的日历 int d ...