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;
这里将10
和20
分别赋给索引0
和1
。
还可以通过索引检索元素值:
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. 数组元素的默认值
初始化时,数组元素会根据数据类型自动分配默认值。这些值表示显式赋值前元素的初始状态。
int
、short
、long
、float
和double
类型的数组默认将所有元素设为零:
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中初始化数组的不同方式。我们学习了如何为任何类型的数组(包括一维和多维数组)声明和分配内存。
此外,我们还了解了用值填充数组的不同方法,包括使用索引单独赋值,以及在声明时用值初始化数组。