一、前言
在Java编程中,数组是一种非常常见且重要 的数据结构,它用于存储和管理多个相同类型的数据元素。本文将探讨Java中的数组,包括一维数组和二维数组,介绍如何声明、初始化、访问和遍历数组,以及如何避免常见的数组异常。
二、内容
2.1 数组
数组是Java中用来存储多个相同类型数据的集合,并通过编号(索引)对这些数据进行统一管理。
(1)概念
- 数组名: 数组在声明时的名称,通过数组名可以引用整个数组。
- 元素: 数组中的每个数据项被称为元素,它们存储在数组中特定的位置。
- 角标/下标/索引: 数组中的元素通过角标或索引来访问,角标从0开始,用于标识元素在数组中的位置。
- 数组的长度: 数组的长度指的是数组中元素的个数,一旦数组被创建,其长度就固定不变。
(2)特点
- 数组是有序排列的,元素在数组中的位置由下标(索引)确定,注意是从0开始。
- 数组是引用数据类型的变量,可以存储基本数据类型或引用数据类型的元素。
- 创建数组对象时,在内存中会开辟一整块连续的空间来存储数组元素。
- 数组的长度一旦确定,就不能修改,如果需要修改长度,需要创建一个新的数组。
(3)分类
- 按维数:
- 一维数组:包含多个元素的线性结构,类似于简单的列表。
- 二维数组:由多个一维数组组成的表格状结构,类似于二维表格。
- 更高维的数组:还可以有三维数组、四维数组等。
- 按数组元素的类型:
- 基本数据类型元素的数组:存储基本数据类型(如
int
、double
等)的数组。 - 引用数据类型元素的数组:存储对象的引用,可以是自定义类的对象或
Java
提供的类的对象。
- 基本数据类型元素的数组:存储基本数据类型(如
2.2 一维数组
(1)声明与初始化
先看代码:
java
// 静态初始化: 数组的初始化和数组元素的赋值操作同时进行
// 创建了一个包含4个整数元素的一维数组,初始值分别是101, 102, 103, 和 104
int[] array1;
array1 = new int[]{101, 102, 103, 104};
// 动态初始化: 数组的初始化和数组元素的赋值操作分开进行
// 创建了一个包含5个整数元素的一维数组,所有元素都会被初始化为0(默认值)
int[] array2 = new int[5];
// 还可以这样:
int[] array3 = {1, 2, 3, 4, 5};
一维数组是Java
中最基本的数据结构之一,用于存储相同类型的数据元素。在声明和初始化一维数组时,有两种常见的方式:静态初始化和动态初始化。
- 静态初始化 :在创建数组的同时,直接指定数组的初始值。例如,
int[] array = new int[]{1001, 1002, 1003, 1004};
这种方式不需要指定数组长度,编译器会自动根据初始值的数量来确定数组长度。 - 动态初始化 :在创建数组时,只指定数组的长度,而没有为数组元素赋初值。例如,
String[] names = new String[5];
这里指定了数组长度为5,数组中的元素将使用默认值进行初始化。
无论你选择哪种初始化方式,一维数组的元素都可以通过索引来访问,索引从0开始,依次递增。例如,
array1[0]
表示数组array1
中的第一个元素。
(2)元素引用
数组的元素可以通过索引(下标)的方式进行访问和修改,数组的索引从0开始,到数组的长度-1结束。
比如:
java
String[] names = new String[4];
names[0] = "张三";
names[1] = "李四";
names[2] = "王五";
names[3] = "赵六";
在上述例子中,names
数组中存储了4个学生姓名,通过索引来访问数组元素,如names[0]
对应的是第一个元素,names[1]
对应的是第二个元素,以此类推。
需要注意的是,当使用索引访问数组元素时,确保索引在有效的范围内,即0到数组长度-1之间,否则将会导致数组索引越界异常(ArrayIndexOutOfBoundsException
)。这是一种运行时异常,应该在编程时避免。
(3)length 属性
数组是一组相同类型的数据元素的集合,每个数组都有一个length
属性,用于表示数组的长度。
java
int[] arr = new int[100];
System.out.println(arr.length); // 输出:100
数组一旦初始化,其长度就是确定的,且长度不可更改。可以通过数组名.length
来获取数组的长度,这对于遍历数组等操作非常有用。
需要注意,数组的长度是数组中元素的个数,而不是声明数组时指定的长度。例如,String[] names = new String[5];
虽然声明了长度为5的数组,但在初始化前数组的长度为0。
(4)遍历数组
遍历数组是常见的操作,可以通过循环结构来遍历一维数组的所有元素。
java
String[] names = new String[4];
names[0] = "张三";
names[1] = "李四";
names[2] = "王五";
names[3] = "赵六";
for (int i = 0; i < names.length; i++) {
System.out.println(names[i]);
}
运行结果:
java
张三
李四
王五
赵六
通过for
循环,我们可以依次访问数组的每个元素。循环变量i
从0开始逐渐增加,直到小于数组长度names.length
为止。在循环体中,通过names[i]
来访问数组的每个元素,并进行相应的操作。
这样的遍历方式适用于所有类型的一维数组,它使得我们可以方便地对数组的所有元素进行操作和处理。
(5)默认初始值
在Java中,如果没有为数组元素显式赋值,那么数组元素将自动根据其数据类型进行默认初始化。
- 对于基本数据类型的数组元素:
- 整型:默认值为0
- 浮点型:默认值为0.0
- char型:默认值为'\u0000',即空字符
- boolean型:默认值为false
- 对于引用数据类型的数组元素:
- 默认值为null
这是一维数组元素默认初始化的规则。当我们创建一个新的数组后,如果没有显式地为数组元素赋值,那么基本数据类型的数组元素将会被初始化为其对应的默认值,而引用数据类型的数组元素将会被初始化为null
。
例如:
对于int
类型的数组int[] numbers = new int[5];
,其中的元素将被默认初始化为5个0。而对于String
类型的数组String[] names = new String[3];
,其中的元素将被默认初始化为3个null
。
这个特性在使用数组前务必注意,以免出现意外的结果。
(6)内存解析
在Java中,一维数组是一个连续的存储区域,数组中的每个元素在内存中都占用一段连续的空间。数组的内存解析可以帮助我们理解数组的存储方式。
假设我们有一个整型数组:
int[] numbers = new int[]{1, 2, 3, 4, 5};
并且假设每个整型数据占用4个字节(32位系统)。
数组的内存解析如下:
java
---------------------------------
| 1 | 2 | 3 | 4 | 5 |
---------------------------------
numbers
数组中的5个整型元素依次存储在内存中的连续位置。numbers[0]
的值为1,numbers[1]
的值为2,以此类推。
了解一维数组的内存解析对于理解数组的索引和遍历机制很有帮助。在内存中,数组元素是连续存储的,而通过索引访问数组元素时,计算机会根据数组的基地址和索引偏移量来定位具体的数组元素。
需要注意的是,在Java
中,数组是一种引用类型,数组名本身并不存储数据,而是存储了数组的地址(也称为引用)。因此,对数组进行传参或赋值时,实际上是传递或复制了数组的地址,而非数组本身的内容。
2.3 二维数组
(1)什么是二维数组?
Java中的二维数组是指数组的数组,也就是说,它是由多个一维数组组成的数据结构。你可以将二维数组理解为一个表格,它由行和列组成,每个单元格都可以通过行索引和列索引来访问。
在定义一个二维数组时,需要指定数组的行数和列数。其语法如下:
java
dataType[][] arrayName = new dataType[rows][columns];
其中:
dataType
是数组中元素的数据类型arrayName
是数组的名称rows
是二维数组的行数columns
是二维数组的列数
举个例子,如果我们想定义一个3行4列的整数类型二维数组,可以这样写:
java
int[][] myArray = new int[3][4];
这将创建一个3行4列的整数二维数组,所有元素初始化为默认值(对于整数类型,初始化为0)。
如果我们希望在定义二维数组的同时初始化其元素,可以使用数组初始化列表(Array initializer)。例如:
java
int[][] myArray = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
这将创建一个3行4列的整数二维数组,并初始化每个元素的值。
要访问二维数组中的特定元素,可以使用索引。例如,要访问第二行第三列的元素,可以这样写:
java
int value = myArray[1][2]; // 第二行第三列的元素为7
需要注意的是,Java中的二维数组实际上是一个数组的数组,每个内部数组的长度可以不同,这意味着可以创建不规则的二维数组。例如:
java
int[][] irregularArray = {
{1, 2},
{3, 4, 5},
{6, 7, 8, 9}
};
(2)声明与初始化
- 正确的方式:
java
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; // 静态初始化
String[][] arr2 = new String[3][2]; // 动态初始化1
String[][] arr3 = new String[3][]; // 动态初始化2
也可以采用类似的方式:
java
int[] arr4[] = new int[][]{{1,2,3},{4,5,9,10},{6,7,8}};
int[] arr5[] = {{1,2,3},{4,5},{6,7,8}};
- 错误的方式:
以下写法是错误的:
- 动态初始化时不指定第二维大小:
java
// String[][] arr4 = new String[][4];
- 不能同时在声明中指定数组大小:
java
// String[4][3] arr5 = new String[][];
- 静态初始化时不能指定数组大小
java
// int[][] arr6 = new int[4][3]{{1,2,3},{4,5},{6,7,8}};
(3)访问元素
访问二维数组元素需要使用两个索引来定位特定的元素。
第一个索引表示要访问的行,第二个索引表示要访问的列。
在Java
中,二维数组是通过行和列组成的,索引从0
开始,因此第一个元素位于索引[0][0]
,第二个元素位于索引[0][1]
,以此类推。
举个例子:
java
public class Main {
public static void main(String[] args) {
// 创建一个3行4列的二维数组并进行初始化
int[][] myArray = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问二维数组元素
int value1 = myArray[0][0]; // 第一行第一列的元素为1
int value2 = myArray[1][2]; // 第二行第三列的元素为7
int value3 = myArray[2][3]; // 第三行第四列的元素为12
// 输出结果
System.out.println("Value 1: " + value1);
System.out.println("Value 2: " + value2);
System.out.println("Value 3: " + value3);
}
}
运行结果:
bash
Value 1: 1
Value 2: 7
Value 3: 12
在上述示例中,我们通过使用两个索引来访问二维数组myArray
中的元素。每个元素可以通过指定其所在行和列的索引来进行访问。
请注意索引是从0开始的,所以第一行的索引是0,第一列的索引也是0。
(4)遍历数组
在Java中,遍历二维数组的元素通常需要使用嵌套循环,一个循环用于遍历行,另一个循环用于遍历每一行中的列。这样可以逐个访问和处理二维数组的所有元素。
下面是几种遍历二维数组元素的常见方法:
- 使用嵌套
for
循环:
java
int[][] myArray = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int i = 0; i < myArray.length; i++) {
for (int j = 0; j < myArray[i].length; j++) {
System.out.print(myArray[i][j] + " ");
}
System.out.println();
}
这段代码将逐行遍历二维数组,并在每一行内遍历每个元素,然后将元素打印输出。
- 使用增强
for
循环(for-each
循环):
java
int[][] myArray = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int[] row : myArray) {
for (int element : row) {
System.out.print(element + " ");
}
System.out.println();
}
增强for循环适用于遍历数组或集合中的所有元素,这里我们使用它来遍历二维数组中的每个元素。
无论使用哪种方法,以上代码都会输出以下结果:
bash
1 2 3
4 5 6
7 8 9
在遍历二维数组时,确保不要越界,尤其是在动态初始化不规则二维数组时。嵌套循环是处理二维数组的常见方法,可以帮助我们逐个访问和处理所有的元素。
(5)默认初始化值
对于初始化方式一:
java
int[][] arr = new int[4][3];
外层元素的初始化值为地址值,都是包含3个整数元素的一维数组,内层元素的初始化值与一维数组初始化相同(这里整数类型的默认初始值为0)。
对于初始化方式二:
java
int[][] arr = new int[4][];
外层元素的初始化值为 null
,因为没有为内层元素指定长度。内层元素的初始化值不能调用,否则会报错。也就是说,内层元素不会初始化,因为外层元素都是 null
,所以,这里我么需要为每个内层元素分别分配一维数组的长度后,才能访问它们。
比如:
java
arr[0] = new int[3];
arr[1] = new int[2];
arr[2] = new int[4];
arr[3] = new int[1];
这将为外层元素 arr[0]
, arr[1]
, arr[2]
, arr[3]
分配了不同长度的内层数组,然后你可以访问这些内层数组的元素。如果在分配内层数组长度之前尝试访问内层元素,将会导致 NullPointerException
,因为外层元素是 null
。
(6)内存解析
在Java中,二维数组实际上是由一维数组组成的数组,因此其内存结构也可以看作是一维数组的数组。让我们来了解二维数组的内存结构:
- 二维数组的声明和创建:
java
int[][] myArray = new int[3][4];
在上述代码中,我们声明并创建了一个3行4列的整数类型二维数组。Java会为这个二维数组分配内存空间。
- 内存结构示意图:
sql
myArray
+---+---+---+---+
| 0 | 0 | 0 | 0 | Row 0
+---+---+---+---+
| 0 | 0 | 0 | 0 | Row 1
+---+---+---+---+
| 0 | 0 | 0 | 0 | Row 2
+---+---+---+---+
在这个示意图中,我们使用矩形来表示整数类型的元素,每个矩形代表二维数组中的一个元素。
- 二维数组
myArray
有3行和4列,表示为3个一维数组,每个一维数组有4个元素。 - 每个元素被初始化为默认值0(对于整数类型)。
注意事项:
- 二维数组在内存中是按照一维数组的形式存储的,外层数组中的每个元素都是一个引用,指向内层数组。内层数组才是真正存储数据的地方。
- 访问元素: 要访问二维数组中的特定元素,可以使用行索引和列索引。例如,要访问第二行第三列的元素,可以使用
myArray[1][2]
。这将访问二维数组中第二个一维数组的第三个元素,其值为0(默认值)。- 动态初始化和不规则数组: Java允许使用动态初始化来创建不规则的二维数组,即每个一维数组的长度可以不同。在这种情况下,每个内部数组的内存位置可能会在堆内存的不同位置。
2.4 数组常见异常
在Java中,数组操作时常见的异常包括:
- 数组索引越界异常(
ArrayIndexOutOfBoundsException
) - 空指针异常(
NullPointerException
)
我们来详细了解这两种异常以及如何避免它们。
(1)数组索引越界
数组索引越界异常会在以下情况下出现:
- 当我们尝试通过索引访问数组元素时,索引超过了数组的有效范围(0到数组长度-1)。
- 当索引为负数时也会出现数组角标越界异常。
例如,在以下代码中,数组arr
有5
个元素,但在for循环中,我们将索引i
从0
递增到5
,导致数组索引越界异常:
java
int[] arr = new int[]{1,2,3,4,5};
for(int i = 0; i <= arr.length; i++){
System.out.println(arr[i]);
}
(2)空指针异常
空指针异常会在以下情况下出现:
- 当我们尝试访问一个空引用的成员时,即引用指向
null
,如arr1 = null; System.out.println(arr1[0]);
。 - 当我们尝试调用空引用的方法时,比如空引用调用
toString()
方法。
例如,在以下代码中,arr1
被赋值为null
,再尝试访问arr1
的元素时会导致空指针异常:
java
int[] arr1 = new int[]{1,2,3};
arr1 = null;
System.out.println(arr1[0]);
(3)避免异常
为了避免数组相关的异常,我们应该在操作数组前进行必要的判断来处理潜在的异常情况。比如:
检查数组长度: 在访问数组元素之前,始终检查数组的长度以确保你不会访问越界的元素。这可以通过使用条件语句来实现,例如 if
语句。
java
if (index >= 0 && index < array.length) {
// 访问数组元素
} else {
// 处理索引越界情况
}
检查数组引用是否为空: 在使用数组之前,确保数组引用不为空,以避免 NullPointerException
。这可以使用条件语句来检查。
java
if (array != null) {
// 访问数组元素
} else {
// 处理空引用情况
}
异常处理机制: 你也可以使用异常处理机制,例如 try-catch
块,来捕获和处理数组相关的异常。这可以帮助你更灵活地处理异常情况,而不是简单地检查和跳过。
java
try {
// 尝试访问数组元素
} catch (ArrayIndexOutOfBoundsException e) {
// 处理数组越界异常
}
三、总结
本文主要介绍了Java中的数组(包括一维数组和二维数组)以及与数组相关的常见异常,以及如何避免这些异常。总的来说,数组是Java中用来存储多个相同类型数据的集合,通过编号(索引)对数据进行统一管理。我们要学会使用数组。