文章目录
- 一、数组的基本认识
- 1.1 数组的概念
- 1.2数组的创建与初始化
- 1.3 数组的使用
- 二、数组的类型 — 引用类型
- 2.1 JVM 内存分布
- 2.2 什么是引用类型
- 2.3 基本类型变量与引用类型变量的区别
- 2.4 Java 中的 null
- 三、数组的应用
- 3.1 保存数据
- 3.2 函数参数
- 3.3 函数返回值
一、数组的基本认识
1.1 数组的概念
数组(Array)是一种用于存储相同类型元素的数据结构。它是一组连续的内存位置,每个位置都存储着一个元素,并通过索引来访问和操作这些元素。数组通常用于存储和处理大量的数据,提供了一种有效的方式来组织和访问数据。
1.2数组的创建与初始化
在 Java 中,数组的创建和初始化可以通过以下几种方式进行:
- 静态初始化
在静态初始化中,可以直接指定数组的元素值,并在创建数组时进行初始化。语法格式如下:
dataType[] arrayName = {value1, value2, value3, ...};
示例:
int[] numbers = {1, 2, 3, 4, 5}; // 创建并初始化一个包含5个整型元素的数组
- 动态初始化
在动态初始化中,需要先创建数组对象,然后通过索引逐个赋值。语法格式如下:
dataType[] arrayName = new dataType[length];
示例:
int[] numbers = new int[5]; // 创建一个包含5个整型元素的数组
numbers[0] = 1; // 给第一个元素赋值
numbers[1] = 2; // 给第二个元素赋值
// ...
- 默认初始化
在默认初始化中,数组的元素会根据其数据类型进行默认初始化。例如,整型数组的元素默认值为0,浮点型数组的元素默认值为0.0,布尔型数组的元素默认值为false,引用类型数组的元素默认值为null。示例:
int[] numbers = new int[5]; // 创建一个包含5个整型元素的数组,默认值为0
- 使用 Arrays 类的 fill() 方法
Arrays 类中的 fill() 方法可以用指定的值填充数组的所有元素。语法格式如下:
Arrays.fill(arrayName, value);
示例:
int[] numbers = new int[5];
Arrays.fill(numbers, 0); // 将数组的所有元素设置为0
这些是在 Java 中创建和初始化数组的常用方法。根据需求和场景的不同,选择合适的方法来创建和初始化数组。
1.3 数组的使用
数组中元素访问:
在 Java 中,可以使用索引来访问数组中的元素。数组的索引从0开始,表示数组中元素的位置。通过指定索引值,可以获取特定位置的元素值,对其进行读取、修改或其他操作。
下面是使用索引访问数组元素的示例:
int[] numbers = {1, 2, 3, 4, 5};
// 读取数组中的元素
int firstElement = numbers[0]; // 获取第一个元素,值为1
int thirdElement = numbers[2]; // 获取第三个元素,值为3
// 修改数组中的元素
numbers[1] = 10; // 将第二个元素的值修改为10
System.out.println("First element: " + firstElement); // 输出: First element: 1
System.out.println("Third element: " + thirdElement); // 输出: Third element: 3
System.out.println("Modified array: " + Arrays.toString(numbers)); // 输出: Modified array: [1, 10, 3, 4, 5]
需要注意的是,数组索引必须在有效范围内,即从0到数组长度减1。如果指定了超出范围的索引,会导致数组越界异常(ArrayIndexOutOfBoundsException
)。
数组的遍历:
在 Java 中,有多种方式可以遍历数组,对数组中的每个元素进行操作。以下是几种常用的数组遍历方法:
- for 循环
使用普通的 for 循环可以遍历数组,通过索引逐个访问数组元素。示例:
int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
int element = numbers[i];
// 对每个元素进行操作
System.out.println(element);
}
- 增强 for 循环(foreach 循环)
增强 for 循环提供了一种简化的语法,可以直接遍历数组中的元素,无需使用索引。示例:
int[] numbers = {1, 2, 3, 4, 5};
for (int element : numbers) {
// 对每个元素进行操作
System.out.println(element);
}
- 使用 Arrays 类的 toString() 方法
使用 Arrays 类的 toString() 方法可以将整个数组转换为字符串表示,方便打印或输出。示例:
int[] numbers = {1, 2, 3, 4, 5};
System.out.println(Arrays.toString(numbers));
二、数组的类型 — 引用类型
2.1 JVM 内存分布
下面是 JVM 的主要内存分布图:
【说明】
-
方法区(Method Area):
方法区是用于存储类的结构信息(如类的字段、方法、构造函数)、运行时常量池、静态变量、即时编译器编译后的代码等数据。它在JVM启动时被创建,并且是所有线程共享的。在较早的JVM版本中,方法区被实现为永久代(Permanent Generation),但在JDK 8及后续版本中,永久代被元空间(Metaspace)取代。 -
虚拟机栈(VM Stack):
虚拟机栈用于存储线程的方法调用和局部变量信息。每个线程在执行时都会创建一个对应的虚拟机栈,栈中的每个元素被称为栈帧(Stack Frame),用于存储方法的局部变量、操作数栈、动态链接、方法返回值等信息。每个方法在执行时都会创建一个对应的栈帧,并随着方法的进入和退出进行入栈和出栈操作。 -
本地方法栈(Native Method Stack):
本地方法栈类似于虚拟机栈,但是它是为执行本地(非Java)方法服务的。它与虚拟机栈的作用相似,但存储的是本地方法的信息。 -
堆(Heap):
堆是用于存储对象实例的区域。在Java程序运行时,动态分配的对象都存储在堆中。堆是Java虚拟机管理的最大一块内存区域,也是所有线程共享的。堆被划分为新生代(Young Generation)和老年代(Old Generation)等不同的区域,其中新生代又包括Eden空间、Survivor空间等。 -
程序计数器(Program Counter):
程序计数器是用于存储当前线程正在执行的字节码指令的地址或索引。它是线程私有的,每个线程都有自己独立的程序计数器。在任何一个时间点,一个线程都只能执行一个方法的代码,也就是所谓的当前方法。程序计数器用于记录线程执行的位置,以便在发生线程切换时能够恢复到正确的执行位置。
此时,我们要理解引用类型,就只需要理解清楚虚拟机栈和堆这两块内存区域。
2.2 什么是引用类型
引用类型(Reference Type)是指在Java中,用于引用对象的类型。与引用类型相对应的是基本类型(Primitive Type),用于直接存储数据的类型,如整数、浮点数、字符等。
引用类型可以是类、接口或数组类型。当我们声明一个引用类型的变量时,实际上在内存中为该变量分配了存储空间,这个存储空间可以用来存储对象的引用(内存地址),而不是实际的对象数据。
引用类型的变量本身占用的内存空间是固定的,不受对象大小的影响,因为它只存储了对象的引用。通过这个引用,我们可以间接地访问和操作对象的实例变量和方法。
2.3 基本类型变量与引用类型变量的区别
简单来说,基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址。
例如下面的代码:
public static void func() {
int a = 10;
int b = 20;
int[] arr = new int[]{1,2,3};
}
其中,a和b都是基本类型变量,而arr是引用类型变量。但是,它们都是方法内部的变量,因此其存储空间的都被分配在func方法的栈帧中。由于a和b是基本类型变量,它们内部保存的就是给自己初始化的值,即1 0 和 20;而arr是引用类型变量,其内部存储的就是自己所引用数组在堆空间中的首地址。
变量a,b,arr的内存分布可用下图进行简单表示:
从图中可以发现,引用变量并不直接存储对象本身,可以简单理解成存储的是对象在堆中空间的起始地址。通过该地址,引用变量便可以去操作对象。有点类似C语言中的指针,但是Java中引用要比指针的操作更简单。
2.4 Java 中的 null
在Java中,null
是一个特殊的值,表示一个引用类型变量没有引用任何对象。它是一个关键字,可以用来将引用类型变量初始化为一个空值。
当一个引用类型变量被赋值为 null
,它表示该变量不引用任何对象,即它不指向任何有效的内存地址。这意味着该变量不能访问该对象的实例变量或调用对象的方法,因为它没有实际的对象引用。
使用 null
有几种常见的情况:
- 在声明一个引用类型变量时,如果还没有具体的对象要赋值给它,可以将其初始化为
null
。
int[] arr = null;
- 当需要释放一个对象的引用时,可以将该引用赋值为
null
。这样,对象就不再被引用,有助于垃圾回收器判断该对象是否可以被回收。
arr = null;
需要注意以下几点:
null
只能被赋值给引用类型变量,不能赋值给基本类型变量,因为基本类型变量不是对象。- 当尝试使用一个值为
null
的引用类型变量进行实例变量访问或方法调用时,会导致空指针异常(NullPointerException
)。因此,在使用引用类型变量之前,需要进行非空检查,以避免空指针异常的发生。
arr[0] = 1; //空指针异常
使用 null
可以帮助我们处理对象的缺失或无效引用的情况,但需要小心使用,以避免空指针异常。
三、数组的应用
3.1 保存数据
数组经常用于保存一组相关的数据。例如,一个学生成绩表可以使用数组来存储每个学生的成绩,一个商品库存列表可以使用数组来存储每个商品的库存数量。
int[] scores = {85, 90, 78, 92, 88}; // 学生成绩数组
int[] inventory = {10, 5, 20, 15}; // 商品库存数组
3.2 函数参数
数组可以作为函数的参数传递,以便在函数中操作和处理数组数据。例如,一个函数可以接受一个整数数组并计算数组中所有元素的总和。
public static int calculateSum(int[] numbers) {
int sum = 0;
for (int i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
return sum;
}
int[] numbers = {1, 2, 3, 4, 5};
int sum = calculateSum(numbers); // 调用函数计算数组元素的总和
System.out.println("数组元素的总和为: " + sum);
3.3 函数返回值
数组也可以作为函数的返回值,用于返回多个相关的值。例如,一个函数可以返回一个包含某个范围内的所有偶数的数组。
public static int[] getEvenNumbers(int start, int end) {
int size = (end - start) / 2 + 1;
int[] evenNumbers = new int[size];
int index = 0;
for (int i = start; i <= end; i++) {
if (i % 2 == 0) {
evenNumbers[index] = i;
index++;
}
}
return evenNumbers;
}
int[] evenNumbers = getEvenNumbers(1, 10); // 调用函数获取范围内的所有偶数
System.out.println("范围内的偶数数组: " + Arrays.toString(evenNumbers));