【Java】数组 (引用类型 与 JVM初识)


数组

基本语法

  1. 动态初始化
数据类型[] 数组名称 = new 数据类型 [长度] { 初始化数据 };

示例:int[] arr = new int[3]{1, 2, 3};

  1. 静态初始化
数据类型[] 数组名称 = { 初始化数据 };

示例:int[] arr = {1, 2, 3};

  • 注:静态初始化时,数组元素个数初始化数据的格式是一致的。

下标越界

我们知道C++中数组下标越界读写是一个未定义行为,结果不可预期,那Java中是这样的吗?

请看代码:

int[] arr = {1, 2, 3};
System.out.println(arr[100]);
  • 执行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
        at Test.main(Test.java:4)

可以看出程序抛出了java.lang.ArrayIndexOutOfBoundsException异常,这就涉及到Java的异常处理机制,所以使用数组一定要下标谨防越界。


for-each 遍历数组

int[] arr = {1, 2, 3};
for (int x : arr) {
    System.out.println(x);
}

// 执行结果
1
2
3

这种写法类似于C++11中范围for的用法,区别在于C++中使用了auto自动类型,而Java中写明了原数据类型。
因此直观的、良好的习惯值得被弘扬,Java 如此设计在其他语言中得到了传承。


引用类型

请看代码:

  1. 参数传递内置类型
public static void main(String[] args) {
    int num = 0;
    func(num);
    System.out.println("num = " + num);
}

public static void func(int x) {
    x = 10;		//在函数内修改
    System.out.println("x = " + x);
}

执行结果:x = 10 num = 0

  1. 参数传递数组类型:
public static void main(String[] args) {
	int[] arr = {1, 2, 3};
	func(arr);
	System.out.println("arr[0] = " + arr[0]);
} 
public static void func(int[] a) {
	a[0] = 10;
	System.out.println("a[0] = " + a[0]);
}

执行结果:a[0] = 10 arr[0] = 10

这让我们联想到C++中参数的传递方式之一的引用传参(&),而Java中很明显对于内置类型的传参与C++一致,但数组类型就有所不同了,从上述案例可以看出,在函数内部修改数组内容, 函数外部也发生改变。

所以:Java中对函数传递数组类型时,相当于按照引用传参

针对于:int[] arr = new int[]{1, 2, 3}

  1. 当我们创建 new int[]{1, 2, 3}的时候,相当于创建了一块内存空间保存三个 int类型的数据。
  2. int[] arr这一语句创建了一个int[]类型变量,这个变量是一个引用类型,其中保存了一个整数(数组的起始内存地址),赋值给它相当于使它获得了操作句柄。
  3. 之后进行传参,在函数中执行如 int[] a = arr的语句,使a数组也获取了与arr一样的这三个类型数据的地址。
  4. 所以对a数组的修改就是针对arr的修改,本质上就是根据地址来获取/修改数据。
  • 总结:所谓的 “引用” 本质上只是存了一个地址。Java 将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到函数形参中。 这样可以避免对整个数组的拷贝,因为数组可能比较长,那么拷贝开销就会很大。

JVM(Java虚拟机)

说到引用就要与内存联系起来,C++中我们知道引用的底层是通过指针实现的,而Java中就涉及了JVM(Java Virtual Machine)内存划分的知识。

我们可以从操作系统的用户地址空间知识进行引申,内存被分为很多个区域,每个区域存放着不同的数据。

在这里插入图片描述

  • 程序计数器 (PC Register):只是一个很小的空间, 保存下一条执行的指令的地址。
  • 虚拟机栈(JVM Stack):重点是存储局部变量表(当然也有其他信息). 我们刚才创建的 int[] arr 这样的存储地址的引用就是在这里保存。
  • 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈的作用类似,只不过保存的内容是Native方法的局部变量。但在有些版本的 JVM 实现中(例如HotSpot),本地方法栈和虚拟机栈是一起的。
  • 堆(Heap):JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} )。
  • 方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法编译出的的字节码就是保存在这个区域。
  • 运行时常量池(Runtime Constant Pool):是方法区的一部分,存放字面量与符号引用。

Native 方法:
JVM 是一个基于 C++ 实现的程序。在 Java 程序执行过程中,本质上也需要调用 C++ 提供的一些函数进行和操作系统底层进行一些交互。因此在 Java 开发中也会调用到一些 C++ 实现的函数。
这里的 Native 方法就是指这些 C++实现的, 再由 Java 来调用的函数。

所以代入一下案例,引用类型int[]aarr数组都是JVM栈数据,new int[]{1,2,3}是堆区数据,a与arr都存放着此堆区数据的地址。

  • 小结:
  1. 局部变量和引用保存在上, new 出的对象保存在上。
  2. 堆的空间非常大, 栈的空间比较小。
  3. 堆是整个 JVM 共享一个, 而栈每个线程具有一份(一个 Java 程序中可能存在多个栈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

giturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值