跳到内容

第 1 卷 / 第 6 章 / 第 1 课

6.1 数组变量、对象与元素 ​

前置知识:变量、类型、条件和循环

预计时间:20 分钟

完成标志:能区分数组变量、数组对象和数组元素,并能处理空数组、null 与非法长度

一个 int score 只能保存一个成绩。若要保存一千名学生的成绩,继续声明 score2 到 score1000 既不可维护,也无法用循环统一处理。Java 数组把固定数量、同一组件类型的值放进一个对象,再用从 0 开始的索引访问每个位置。

1. 声明变量不等于创建数组 ​

java
int[] scores;          // 声明一个能引用 int 数组的变量
scores = new int[5];   // 创建长度为 5 的数组对象并保存其引用

也可以合写:

java
int[] scores = new int[5];

这里有三个不同概念:

名称例子含义
组件类型int每个位置能保存什么类型
数组类型int[]变量能引用哪类数组
数组对象new int[5]运行时创建的 5 个组件

scores 变量保存的是数组引用,不是把五个整数直接写进变量。数组是对象,但创建后长度固定。需要不同长度时,只能创建另一个数组并复制所需元素。

Java 语言规定了索引行为,却没有要求程序员依赖某种对象物理布局。可以把索引理解为相对第一个元素的偏移:第一个偏移是 0,所以长度为 5 的合法索引是 0、1、2、3、4。

2. 新数组先获得默认值 ​

java
int[] counts = new int[3];
boolean[] flags = new boolean[2];
String[] names = new String[2];

System.out.println(counts[0]); // 0
System.out.println(flags[0]);  // false
System.out.println(names[0]);  // null

数组组件会被默认初始化:

组件类型默认值
byte、short、int、long对应类型的 0
float、double对应类型的正零
char\u0000
booleanfalse
引用类型null

这和方法内的普通局部变量不同。局部变量必须先明确赋值才能读取;新数组的组件已经由运行时初始化。

3. 写入和读取都要通过合法索引 ​

java
int[] scores = new int[5];
scores[0] = 85;
scores[1] = 92;
scores[2] = 78;
scores[3] = 95;
scores[4] = 88;

System.out.println(scores[2]); // 78
System.out.println(scores.length); // 5

length 是数组的只读字段,没有括号。对长度 n 的数组,索引必须满足:

text
0 <= index && index < n

访问 scores[-1] 或 scores[5] 都会抛 ArrayIndexOutOfBoundsException。Java 会在运行时检查每次数组访问,不会自动扩容或把越界写入别处。

4. 初始化器适合已知的少量值 ​

声明时可以同时创建并填值:

java
int[] scores = {85, 92, 78, 95, 88};

需要在声明之后替换整个数组时,要写出数组类型:

java
int[] scores = new int[0];
scores = new int[]{60, 70, 80};

下面不能编译:

java
// scores = {60, 70, 80}; // 数组初始化器不能单独作为赋值右侧

也不能把长度和初始化列表同时写在一个数组创建表达式里:

java
// int[] scores = new int[3]{60, 70, 80}; // 编译错误

5. 空数组、null 和非法长度是三种状态 ​

java
int[] empty = new int[0];
System.out.println(empty.length); // 0

int[] missing = null;
  • 空数组是存在的对象,长度为 0,没有任何合法索引。
  • null 表示变量当前没有引用数组;读取 missing.length 会抛 NullPointerException。
  • new int[-1] 在运行时抛 NegativeArraySizeException。

API 设计中应明确区分“没有提供数组”和“提供了一个没有元素的数组”。若两者含义相同,通常优先接收空数组,调用方更容易直接遍历。

6. 数组变量赋值会产生别名 ​

java
int[] first = {10, 20, 30};
int[] second = first;

second[0] = 99;
System.out.println(first[0]); // 99

这没有创建第二个数组。first 和 second 保存了同一个数组对象的引用,所以通过任一变量修改组件,另一边都能看到。

若需要独立数组,要复制组件。下一课之后会比较 clone、Arrays.copyOf 和 System.arraycopy。对于引用类型数组,这些操作复制的仍是组件中的引用,不会自动复制每个元素对象。

语言差异窗口 ​

  • C++ 内建数组的存储期取决于声明位置和分配方式,并不只有“栈数组”和“堆数组”两种。现代 C++ 通常优先使用 std::array 表示固定长度、std::vector 表示动态长度。
  • Python 常用 list,它可以增长,也允许混合类型;类型标注和工程约定可进一步约束元素类型。它不是 Java 数组的等价物。
python
scores = [85, 92, 78, 95, 88]
print(scores[2])  # 78
scores.append(100)

专业速查 ​

目的Java 写法边界
只声明变量int[] values;使用前必须赋值
创建定长数组new int[5]长度不能为负
同时填值int[] values = {1, 2};简写仅用于声明
后续替换数组values = new int[]{1, 2};原对象是否仍被引用取决于别名
读取长度values.lengthvalues 不能为 null
访问组件values[index]0 <= index < length

离开这一页前 ​

把 int[] values、new int[5] 和 values[0] 分别标成数组变量、数组对象和组件访问,再解释空数组、null 与负长度在哪个阶段、以何种方式失败。最后画出 second = first 后的两个变量和一个数组对象。

下一页用循环访问每个元素,并检查正序、倒序和空数组的边界。

→ 下一步:数组的索引与遍历

规范入口:JLS §10:数组。

Built with VitePress | Software Systems Atlas