【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 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭20 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果41 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
duration~2 小时前
Maven随笔
java·maven
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD2 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构