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. How to get the return value of the setTimeout inner function in js All In One

    How to get the return value of the setTimeout inner function in js All In One 在 js 中如何获取 setTimeout ...

  2. 『现学现忘』Git基础 — 37、标签tag(二)

    目录 5.共享标签 6.删除标签 7.修改标签指定提交的代码 8.标签在.git目录中的位置 9.本文中所使用到的命令 提示:接上一篇文章内容. 5.共享标签 默认情况下,git push 命令并不会 ...

  3. 前后端代码分离开发(Vue)

  4. 一、SQL介绍

    Mysql 简单来说,数据库就是一个存储数据的仓库,它将数据按照特定的规律存储在磁盘上.为了方便用户组织和管理数据,其专门提供了数据库管理系统.通过数据库管理系统,用户可以有效的组织和管理存储在数据库 ...

  5. 关于Dockerfile的写法

    Dockerfile是用来自定义构建镜像的文件. Dockerfile: FROM nginx RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/ht ...

  6. 2022NISACTF--WEB

    easyssrf 打开题目,显示的是 尝试输入, 发现输入flag有东西 读取文件 访问下一个网站 读取文件 不能以file开头 直接伪协议,base64解码 checkIn 奇怪的unicode编码 ...

  7. Go语言核心36讲11

    至今为止,我们讲过的集合类的高级数据类型都属于针对单一元素的容器. 它们或用连续存储,或用互存指针的方式收纳元素,这里的每个元素都代表了一个从属某一类型的独立值. 我们今天要讲的字典(map)却不同, ...

  8. 读书笔记:A Philosophy of Software Design

    今天一位同事在斯坦福的博士生导师John Ousterhout (注,Tcl语言的设计者)来公司做了他的新书<A Philosophy of Software Design>的演讲,介绍了 ...

  9. 基于python的数学建模---时间序列

    JetRail高铁乘客量预测--7种时间序列方法 数据获取:获得2012-2014两年每小时乘客数量 import pandas as pd import numpy as np import mat ...

  10. npm安装hexo报错

    报错提示 npm WARN saveError ENOENT: no such file or directory, open '/home/linux1/package.json' npm noti ...