1. 概述

数组是一种允许我们存储和操作相同类型元素集合的数据结构。数组具有固定大小,在初始化时确定,运行时无法改变

本教程将介绍如何声明数组,并探讨不同的数组初始化方式及其细微差别。

2. 理解数组

在Java中,数组是能存储多个相同数据类型元素的对象。我们可以通过索引(从0开始的数字位置)访问数组中的所有元素。数组的长度表示它能容纳的元素总数:

数组元素和索引的表示

上图展示了一个包含8个元素的一维数组。

⚠️ 注意:如果尝试访问数组有效索引范围外的元素,会抛出ArrayIndexOutOfBoundsException异常

3. 声明和初始化一维数组

我们可以通过指定数据类型后跟方括号和数组名来声明数组:

int[] numbers;

上述代码声明了一个未初始化的int类型数组。

也可以将方括号放在数组名后,但这种方式较少使用

int numbers[];

数组必须初始化后才能使用。初始化需要使用new关键字分配内存并指定数组长度:

var numbers = new int[7];

这里初始化了一个能容纳7个int类型元素的numbers数组。

初始化后,可以通过索引为单个元素赋值:

numbers[0] = 10;
numbers[1] = 20;

这里将1020分别赋给索引01

还可以通过索引检索元素值:

assertEquals(20, numbers[1]);

断言索引1处的元素值为20

可以在单步中声明并初始化数组

int[] numbers = new int[5];

数组的长度始终固定,初始化后不能扩展。

也可以使用变量指定数组长度:

int length = 7;
int[] numbers = new int[length];

这里声明了一个变量作为数组长度。重要提示:length只能是int类型。任何非int类型都会导致不兼容类型错误。

4. 声明未知大小的数组

声明数组时无需知道大小。我们可以将数组赋值为null或空数组

int[] numbers = null;

int[] numbers = new int[0];

初始化时必须知道大小,因为Java虚拟机需要为其预留连续内存块。如前所述,可以使用new关键字初始化数组:

int[] numbers =  new int[5];

如果需要调整数组大小,可以创建更大的数组并将原数组元素复制到新数组:

int newSize = 10; // 新期望大小
int[] newArray = new int[newSize];

// 将旧数组元素复制到新数组
System.arraycopy(numbers, 0, newArray, 0, numbers.length);

numbers = newArray; // 引用新数组

如果允许使用ArrayList,动态调整大小应始终使用ArrayList

// 无需指定大小即可动态添加元素
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

ArrayList内部使用数组和System.arrayCopy()支持动态调整大小。

5. 数组元素的默认值

初始化时,数组元素会根据数据类型自动分配默认值。这些值表示显式赋值前元素的初始状态。

intshortlongfloatdouble类型的数组默认将所有元素设为零:

int[] array = new int[5];
assertArrayEquals(new int[] { 0, 0, 0, 0, 0 }, array);

对于boolean数组,所有元素默认为false

boolean[] array = new boolean[5];
assertArrayEquals(new boolean[] { false, false, false, false, false }, array);

对于对象类型数组(如String),所有元素默认为null

String[] array = new String[5];
assertArrayEquals(new String[] { null, null, null, null, null }, array);

⚠️ 注意:当使用或访问未显式赋值的数组元素时,实际使用的是默认值

6. 使用值初始化数组

还可以在初始化时为数组赋值。这通常称为数组字面量

String[] brand = new String[] { "Toyota", "Mercedes", "BMW", "Volkswagen", "Skoda" };

这里初始化了一个包含5个品牌名称的字符串数组。花括号内的元素总数决定了数组的长度/大小。

对于基本数据类型,可以省略数组类型:

int[] array = { 1, 2, 3, 4, 5 };

使用var关键字进行类型推断时,不能在没有new关键字的情况下初始化数组字面量

var array = {1, 2, 3, 4, 5}; // 编译错误

要使用var关键字与数组字面量,必须引入new关键字:

var arr = new int[]{1,2,3,4,5};

编译器能推断出这是一个整数数组。

7. 使用Arrays.fill()添加值

java.util.Arrays类有多个名为fill()的方法,接受不同类型的参数,用相同值填充整个数组:

long array[] = new long[5];
Arrays.fill(array, 30);

当需要用相同值更新多个数组元素时,此方法很有用。

该方法还有多个重载版本,可将数组的特定范围设置为特定值:

int[] array = new int[5];
Arrays.fill(array, 0, 3, -50);

这里fill()方法接受初始化数组、填充起始索引、结束索引(不包括)和值作为参数。数组的前三个元素被设为-50,其余元素保持默认值0。

⚠️ **如果起始和结束索引之间的范围大于数组大小,会抛出ArrayIndexOutOfBoundsException**。

8. 使用Arrays.copyOf()复制数组

Arrays.copyOf()方法通过复制现有数组元素创建新数组。该方法有多个重载版本,接受不同类型的参数。

快速示例:

int[] array = { 1, 2, 3, 4, 5 };
int[] copy = Arrays.copyOf(array, 5);

注意事项:

  • 方法接受两个参数:源数组和新数组的期望长度
  • 如果长度大于要复制的数组长度,额外元素将使用其类型的默认值初始化
  • 如果源数组未初始化,会抛出NullPointerException

9. 使用Arrays.setAll()添加值

Arrays.setAll()方法使用生成器函数设置数组的所有元素。当需要向数组添加遵循特定模式或逻辑的值时,此方法很有用。

使用Arrays.setAll()的示例:

int[] numbers = new int[5];
Arrays.setAll(numbers, i -> i * 2);
assertArrayEquals(new int[] {0, 2, 4, 6, 8}, numbers);

这里创建了一个int类型数组,并用setAll()方法填充偶数。

更复杂的生成器函数示例:

int[] array = new int[20];
Arrays.setAll(array, p -> p > 9 ? 0 : p);

// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

生成器函数将数组的前十个元素设为其索引,其余元素设为0。

如果生成器函数为null,会抛出NullPointerException

10. 使用ArrayUtils.clone()克隆数组

使用Apache Commons Lang 3中的ArrayUtils.clone() API,通过创建另一个数组的直接副本来初始化数组:

char[] array = new char[] {'a', 'b', 'c'};
char[] copy = ArrayUtils.clone(array);

注意:此方法为所有基本类型提供了重载版本

11. 声明和初始化二维数组

声明二维数组的方式:

int[][] matrix;

这里通过指定两组方括号声明了一个int类型的二维数组。初始化数组并指定行数和列数:

matrix = new int[2][5];

可以将其视为2行5列的数组。

使用嵌套索引为单个元素赋值,第一个索引表示行,第二个索引表示列

matrix[0][0] = 10;
matrix[0][1] = 20;
matrix[0][2] = 30;
matrix[0][3] = 40;
matrix[0][4] = 50;
matrix[1][0] = 60;
matrix[1][1] = 70;
matrix[1][2] = 80;
matrix[1][3] = 90;
matrix[1][4] = 100;

通过指定行和列索引检索二维数组中的值:

assertEquals(20, matrix[0][1]);

这里检索第0行第1列的数组值。

也可以使用较少见的语法初始化二维数组:

int[] array[] = new int[5][5];

还可以使用嵌套花括号用字面量初始化二维数组:

 int [][] twoDArray = { { 1, 2, 3 }, { 4, 5, 6 } };

这里初始化了一个包含两个一维数组的int类型二维数组,表示数组的两行。

12. 使用for循环添加值

可以使用循环为数组的每个元素单独赋值。当需要基于特定条件或计算填充数组时,这种方法很有用。

基于循环的示例:

int[] array = new int [3];
for (int i = 0; i < array.length; i++) {
    array[i] = i + 2;
}

这里使用循环为每个元素赋值,将i + 2赋给索引i处的元素。

对于多维数组,可以使用嵌套循环遍历每个维度并为元素赋值:

int[][] matrix = new int[3][4];
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        matrix[i][j] = i * 4 + j;
    }
}

这里使用嵌套for循环分别遍历行和列,为每个元素赋值i * 4 + j,该值基于行和列索引计算。

13. 使用Stream API初始化数组

Stream API提供了从元素流创建数组的便捷方法,包括Arrays.stream()IntStream.of()DoubleStream.of()等。这些方法允许我们用指定值初始化数组。

使用Instream.of()方法初始化并填充数组的示例:

 int[] values = IntStream.of(1, 2, 3, 4, 5).toArray();

这里创建了一个包含5个值的IntStream,并用toArray()方法将其转换为整数数组。

还可以使用Stream API初始化更高维度的数组。初始化二维数组的示例:

int[][] matrix = IntStream.range(0, 3)
  .mapToObj(i -> IntStream.range(0, 4).map(j -> i * 4 + j).toArray())
  .toArray(int[][]::new);

首先使用IntStream.range(0, 3)生成整数流,对应matrix的行索引。然后使用mapToObj()将原始流中的每个整数转换为整数数组。接着生成另一个整数流,对应matrix每行的列索引。

最后使用toArray(int[][]::new)方法将整数数组流转换为二维数组,该方法引用会创建新的二维数组。

14. 更高维数组

还可以有超过二维的数组。根据具体用例,可以有三维数组、四维数组等。方括号的数量决定维度。

声明和初始化三维数组的方式:

int[][][] threeDArray = new int[3][4][5];

这里有三个方括号表示这是一个三维数组。第一个方括号[3]表示数组的深度,指示三维数组包含的二维数组数量。这里三维数组包含三个二维数组。

向第一个二维数组添加元素:

threeDArray[0][0][1] = 4;

这里第一个索引[0]表示三维数组中的第一个二维数组,第二个索引[0]表示第一个二维数组的第一行,第三个索引[1]表示第一行中的第二列。

向第二个二维数组添加元素:

threeDArray[1][0][0] = 6;

首先通过索引[1]指示第二个二维数组,然后在该二维数组的第一行第一列添加元素。

简单来说,三维数组是二维数组的数组

类似地,四维数组本质上是三维数组的数组。

15. 总结

本文探讨了Java中初始化数组的不同方式。我们学习了如何为任何类型的数组(包括一维和多维数组)声明和分配内存。

此外,我们还了解了用值填充数组的不同方法,包括使用索引单独赋值,以及在声明时用值初始化数组。


原始标题:Initializing Arrays in Java | Baeldung