🌀🌀🌀作者:@小鱼不会骑车
🍁🍁🍁专栏:《java练级之旅》
🎀🎀🎀个人简介:一名专科大一在读的小比特,努力学习编程是我唯一的出路😎😎😎
🙈🙈🙈作者心里话![]()
《java练级之路》之数组为什么成为引用类型
小鱼一直都是秉承着“开开心心看博客,快快乐乐学知识”这个观点来给大家用一些接地气的话进行讲解,可能会有人觉得小鱼太墨迹了,小鱼这里也很乐意接受大家的意见,会进行采纳,大家也可以指出小鱼的不足,小鱼也会积极的进行改变,总之,愿我们越来越优秀
前言
在这篇文章中,小鱼会细致的讲解大家在初识java时对于java中数组的认知,前面一篇文章讲解了数组的创建及初始化,那么这片文章呢,就会讲到关于数组在内存中是如何存储的,准备好了吗各位开始发车了😜
(数组是引用类型)
🍎 初识JVM的内存分布
💡在我们的java中,内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如:
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。
- 程序运行时代码需要加载到内存
- 程序运行产生的中间数据要存放在内存
- 程序中的常量也要保存
- 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
因此JVM也对所使用的内存按照功能的不同进行了划分:
总共有五部分,
- 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
- 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量.在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的。
- 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
但是我们在学习这一篇的内容时只会涉及到栈区和堆区,稍后会进行详细讲解。
🍎 基本类型变量与引用类型变量
🍓1. 基本数据类型和引用类型变量
🐵我们接下来用三种方式开辟数组
int[]array1={1,2,3,4};//开辟一个静态数组
int[]array2=new int[]{1,2,3,4};//同上(new int []可以舍去)
int[]array3=new int[4];//开辟一个动态数组
这是一个int类型,在内存中的存放方式如图
int a=10;
a在main函数里是局部变量,所以在栈上分配内存。
那么我们的数组呢?在内存中是如何存储的?🧐
在内存中,堆区比栈区大一些❗️❗️❗️
👉大家看,我们的第一个和第二个开辟数组的方式其实是一样的,都是new一个空间出来,那么new出来的空间是哪里的呢?其实就是堆上的,我们从堆上开辟了一块内存,用来存放这个而数组的值,
我们假设array1在堆中开辟内存的地址是0x123,那么我们变量array1存放的就是0x123,也就是存放堆上开辟空间的地址。
🌈大家刚才看到了
int a=10
,在栈上存放的是int类型的数据,但是
int[]arry1
是一个引用类型,存放的是堆上开辟的内存的地址,那我们就可以称array1这种变量为引用变量(引用),这时候我们就可以通过array1引用变量中存放的地址,找到这块开辟的内存空间,
✅专业术语:array1指向了一个对象,这个对象就是在堆上开辟的内存
就这样,我们就可以通过这个地址找到这块空间。
int a=10;
int []array={1,2,3,4};
✅总结上述代码
- a,array都是函数内部创建的变量,因此,其空间都在main方法对应的栈帧中分配。
- a是内置类型的变量,因此其空间保存的就是给该变量初始化的值。
- array是数组类型的引用变量,其内部保存的内容可以简单理解成,是数组在堆空间中的首地址。
🍎 再谈引用变量
🍓 1. 第一道基础题
接下来给大家看一串代码
int[]array1={1,2,3,4};
array1[0]=99;
int[]array2=array1;
array2[0]=100;
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
🐻大家猜第一个打印什么第二个打印什么?
大家现在可以暂时思考一下,心中有答案之后再往下划,看看答案是不是跟自己想的一样。
🐧好的,接下来就会给大家进行解析,我们先把代码拷贝到画图板
1️⃣我们现在进行了第一步,开辟空间,
2️⃣将首元素改为99
3️⃣将array1的值给array2
我们将a赋值给b我们的b就是10,那我们把array1给array2呢?因为我们的array1中存放的是地址,所以我们把array1的值赋值给array2就是把地址赋值给array2,如图
所以我们的array2指向的空间也是地址为0x123的空间,
4️⃣将array2[0]的值改为100
因为我们array1和array2指向的同一块空间,所以修改array2就就是修改array1,所以我们最后的输出就是{100, 2, 3, 4}
综合上述
🍓 2. 第二道基础题
🙈好的,大家再来看这串代码(小鱼又要挖坑了呦)
int[]array1={1,2,3,4};
int[]array2={11,22,33,44};
array1=array2;
array1[0]=1234;
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
让我们来思考思考,这个会输出什么呢?
答案就在下面
输出
大家想到的答案和这个一样嘛?
接下来小鱼给大家讲解
1️⃣第一步依旧是开辟一块空间
2️⃣就是把array2赋值给array1
这样我们array1的存放的地址和array2存放的地址就一样,我们就可以通过array1找到array2地址指向的空间,
3️⃣ 我们将array[0]的值改为1234,
🐧好的,现在我们现在就可以发现,我们的array1和array2指向的是同一块空间,并且我们的array1将下标为0的元素改为了1234,于是最后输出就得到了我们上面的答案,当然,此时由于我们的array1找不到之前的空间了,所以之前开辟的空间就会被系统自动回收,不用担心内存泄漏
✅结论
当两个引用同时指向一块对象时,通过其中任意一个引用去访问该对象并修改该对象的值,另一个引用去访问的时候,值也是被改变的
💡可以理解为对象时一个“空调”,我用这个“遥控器1”(array1)去修改它的温度之后,我的“遥控器2”(array2)再去访问这个空调,就是已经被修改的温度,当我的“遥控器2”去修改也是同理
🍎 认识null
🍓1. java中null注意事项
🌈在java当中局部变量一定要初始化不然会报错❗️❗️❗️
🍰我们的b是int类型可以赋值个0初始化,但是我们的array4呢?他是个引用,他存的是地址,该怎么做才能让他不去指向别的变量呢。我们可以用null(小写)这个就是空指针,当我们置成空指针时,array4就不指向任何对象了
int b=0;
int[]array4=null;
System.out.println(b);
System.out.println(array4);
☀️但是呢,在我们初始化为空指针后,如果我想要访问它的第0个元素,就会报错。
int[]array4=null;
System.out.println(array4[0]);
🍒这个就是咱们以后会经常涉及到的问题,也可以成为NPE,意思是空指针异常,当出现这个问题的时候,就需要去检查了,这里是8
😾这时候我们就需要根据提示
🍀我们的发现array4是null,如果我们的引用是null我们通过这个引用不管去做任何事他都会报错,
int[]array4=null;
System.out.println(array4.length);
🍎 总结
- 我们的数组是存放在哪里
- 如何访问到这个数组
- 数组的值是如何被修改的
- 在访问数组时需要注意的事项
- 如何避免空指针异常