【Java】数组基础解析与应用指南

一、前言

在Java编程中,数组是一种非常常见且重要 的数据结构,它用于存储和管理多个相同类型的数据元素。本文将探讨Java中的数组,包括一维数组和二维数组,介绍如何声明、初始化、访问和遍历数组,以及如何避免常见的数组异常。


二、内容

2.1 数组

数组是Java中用来存储多个相同类型数据的集合,并通过编号(索引)对这些数据进行统一管理

(1)概念

  1. 数组名: 数组在声明时的名称,通过数组名可以引用整个数组。
  2. 元素: 数组中的每个数据项被称为元素,它们存储在数组中特定的位置。
  3. 角标/下标/索引: 数组中的元素通过角标或索引来访问,角标从0开始,用于标识元素在数组中的位置。
  4. 数组的长度: 数组的长度指的是数组中元素的个数,一旦数组被创建,其长度就固定不变。

(2)特点

  1. 数组是有序排列的,元素在数组中的位置由下标(索引)确定,注意是从0开始。
  2. 数组是引用数据类型的变量,可以存储基本数据类型或引用数据类型的元素。
  3. 创建数组对象时,在内存中会开辟一整块连续的空间来存储数组元素。
  4. 数组的长度一旦确定,就不能修改,如果需要修改长度,需要创建一个新的数组。

(3)分类

  1. 按维数:
    • 一维数组:包含多个元素的线性结构,类似于简单的列表。
    • 二维数组:由多个一维数组组成的表格状结构,类似于二维表格。
    • 更高维的数组:还可以有三维数组、四维数组等。
  2. 按数组元素的类型:
    • 基本数据类型元素的数组:存储基本数据类型(如intdouble等)的数组。
    • 引用数据类型元素的数组:存储对象的引用,可以是自定义类的对象或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(对于整数类型)。

注意事项

  1. 二维数组在内存中是按照一维数组的形式存储的,外层数组中的每个元素都是一个引用,指向内层数组。内层数组才是真正存储数据的地方。
  2. 访问元素: 要访问二维数组中的特定元素,可以使用行索引和列索引。例如,要访问第二行第三列的元素,可以使用myArray[1][2]。这将访问二维数组中第二个一维数组的第三个元素,其值为0(默认值)。
  3. 动态初始化和不规则数组: Java允许使用动态初始化来创建不规则的二维数组,即每个一维数组的长度可以不同。在这种情况下,每个内部数组的内存位置可能会在堆内存的不同位置。

2.4 数组常见异常

在Java中,数组操作时常见的异常包括:

  • 数组索引越界异常(ArrayIndexOutOfBoundsException
  • 空指针异常(NullPointerException

我们来详细了解这两种异常以及如何避免它们。

(1)数组索引越界

数组索引越界异常会在以下情况下出现:

  • 当我们尝试通过索引访问数组元素时,索引超过了数组的有效范围(0到数组长度-1)。
  • 当索引为负数时也会出现数组角标越界异常。

例如,在以下代码中,数组arr5个元素,但在for循环中,我们将索引i0递增到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中用来存储多个相同类型数据的集合,通过编号(索引)对数据进行统一管理。我们要学会使用数组。

相关推荐
程序员蜗牛16 小时前
9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
后端
yeyong16 小时前
咨询kimi关于设计日志告警功能,还是有启发的
后端
库森学长16 小时前
2025年,你不能错过Spring AI,那个汲取了LangChain灵感的家伙!
后端·openai·ai编程
爱吃苹果的日记本16 小时前
开学第一课
java
Java水解17 小时前
Spring Boot 启动流程详解
spring boot·后端
学历真的很重要17 小时前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
渣哥17 小时前
Java 集合框架详解:常见集合类及分类方式
java
转转技术团队17 小时前
让AI成为你的编程助手:如何高效使用Cursor
后端·cursor
shellvon17 小时前
你怎么被识别的?从TLS到Canvas的设备追踪术
后端·算法
yinke小琪17 小时前
消息队列如何保证消息顺序性?从原理到代码手把手教你
java·后端·面试