1. 用一个数组管理一组数据
数组用于保存一组相同类型的数据。例如,保存 5 个学生的成绩,不必分别定义 5 个变量:
int[] scores = {70, 80, 85, 60, 90}; 这里,int[] 是数组类型,scores 是数组变量,数组中有 5 个 int 元素。
可以把数组理解为一排连续编号的格子:每个格子保存一个元素,通过编号直接访问。这个编号叫作下标(索引),从 0 开始。
下标: 0 1 2 3 4 元素: 70 80 85 60 90 一个数组创建后,长度就固定了;元素的值可以修改。
1.1 创建与初始化
动态初始化:指定长度,元素先使用默认值。
int[] numbers = new int[3]; // 3 个 int 元素,初始值都是 0 double[] prices = new double[5]; // 5 个 double 元素 String[] words = new String[3]; // 3 个 String 元素 通用格式是 元素类型[] 数组名 = new 元素类型[长度];。new 在这里用于创建数组。
“动态初始化”并不意味着长度可以随意增长,而是创建时指定长度。
静态初始化:直接给出元素,长度由元素个数决定。
int[] a = new int[]{10, 20, 30}; int[] b = {10, 20, 30}; // 声明时可以简写 String[] words = {"Hello", "Java"}; 元素值必须能赋给数组的元素类型。指定元素时,不再在方括号中填写长度:
// 错误:不能同时指定长度和初始化列表 // int[] a = new int[3]{10, 20, 30}; 声明和赋值可以分开,但分开后不能只写 {...}:
int[] a; a = new int[3]; a = new int[]{10, 20, 30}; // 错误:这种简写只能在声明时使用 // a = {10, 20, 30}; int a[] 也是合法写法,但推荐 int[] a,更容易看出变量的类型是 int[]。
1.2 数组元素的默认值
使用 new 类型[长度] 创建数组后,即使没有逐个赋值,元素也已经有默认值:
| 元素类型 | 默认值 |
|---|---|
byte、short、int、long | 0 |
float | 0.0f |
double | 0.0 |
char | '\u0000',不是字符 '0',也不是空格 |
boolean | false |
引用类型,例如 String | null,表示没有引用对象 |
注意区分:int[] a; 只是声明了局部变量,没有创建数组,也不能直接使用这个尚未赋值的变量;int[] a = new int[3]; 才创建了包含 3 个零的数组。
1.3 访问元素与获取长度
int[] a = {10, 20, 30}; System.out.println(a[0]); // 10:读取第一个元素 a[1] = 99; // 修改第二个元素 System.out.println(a[1]); // 99 System.out.println(a.length); // 3 -
a.length是元素个数,没有括号。 - 合法下标为
0到a.length - 1。 - 最后一个元素是
a[a.length - 1],不是a[a.length]。
对于上面的数组,访问 a[3] 或 a[-1] 都会发生 ArrayIndexOutOfBoundsException(数组下标越界异常)。异常可以先理解为程序运行时报告的错误。
1.4 遍历数组
遍历就是依次访问数组中的所有元素。
需要下标或需要修改元素时,用普通 for 循环:
int[] a = {10, 20, 30}; for (int i = 0; i < a.length; i++) { a[i] += 1; System.out.println(a[i]); // 依次输出 11、21、31 } 条件写成 i < a.length,不能写成 i <= a.length;使用 a.length 也比把长度写死更方便。
只需要依次读取元素时,可以用增强 for(for-each):
int[] a = {10, 20, 30}; for (int x : a) { System.out.println(x); } 每次循环,x 都取得当前元素的值,它不是下标。对于这里的 int 数组,给 x 赋值只会改变循环变量,不会修改数组元素;要修改元素,应使用 a[i]。
1.5 一次打印整个数组
Arrays 是 java.util 包中的数组工具类,提供了操作数组的方法。使用前在 Java 文件的类定义上方写:
import java.util.Arrays; Arrays.toString(a) 把一维数组的内容转换成便于阅读的字符串:
int[] a = {10, 20, 30}; String text = Arrays.toString(a); System.out.println(text); // [10, 20, 30] 直接 System.out.println(a) 不会按上面的形式输出数组元素。
2. 数组变量保存的是引用
2.1 先分清“变量”和“数组对象”
int number = 10; int[] a = {1, 2, 3}; -
number是基本类型变量,直接保存数值10。 -
a是引用类型变量,保存的是数组对象的引用,不是三个元素本身。
引用可以暂时理解为找到对象的“地址线索”,不必把它当成可直接操作的真实内存地址。
number 中保存:10 a 中保存的引用 ──→ 数组对象 [1, 2, 3] JVM(Java 虚拟机)负责运行 Java 程序,其运行时内存按用途划分:
| 区域 | 本章需要知道的作用 |
|---|---|
| 虚拟机栈 | 方法调用时创建栈帧,保存局部变量等执行信息;方法结束时对应栈帧出栈 |
| 堆 | 存放数组等对象 |
| 程序计数器 | 记录当前线程执行字节码的位置 |
| 本地方法栈 | 为本地方法服务;本地方法是由 Java 以外的语言实现的方法 |
| 方法区 | 保存类信息、方法字节码等数据 |
本章重点是栈和堆。 按这里的入门模型,方法内的局部变量 number、a 位于该方法的栈帧中,而 a 引用的数组对象位于堆中。其他区域的内部细节暂不展开。
2.2 数组赋值不会复制元素
int[] a = {1, 2, 3}; int[] b = a; b[0] = 99; System.out.println(a[0]); // 99 b = a 复制的是引用,因此两个变量指向同一个数组:
a ──┐ ├──→ [99, 2, 3] b ──┘ 通过 a 或 b 修改元素,改的都是同一个数组对象。
但重新给引用变量赋值,只会改变这个变量的指向:
b = new int[]{7, 8}; System.out.println(a[0]); // 99:a 仍指向原数组 System.out.println(b[0]); // 7:b 指向新数组 System.out.println(a.length); // 3 System.out.println(b.length); // 2 这里不是原数组长度从 3 变成了 2,而是 b 改为引用另一个数组。
2.3 null:目前没有引用任何数组
int[] a = null; // System.out.println(a[0]); // 空指针异常 // System.out.println(a.length); // 空指针异常 null 表示空引用。此时没有可访问的数组对象,读取元素或长度会发生 NullPointerException(空指针异常)。null 也不等于数值 0。
要区分下面两种情况:
| 写法 | 含义 |
|---|---|
int[] a = null; | 没有引用数组,不能读取 a.length |
int[] a = new int[0]; | 引用了长度为 0 的数组,a.length 为 0,但没有可访问的元素 |
3. 数组与方法配合使用
下面的 public static 方法定义在类中,与 main 并列;调用语句放在 main 等方法内部。
3.1 作为参数:传入的是引用的副本
Java 的参数传递都是值传递:基本类型复制数值,引用类型复制引用值。
先看基本类型:
public static void changeNumber(int x) { x = 10; } int number = 0; changeNumber(number); System.out.println(number); // 0 形参 x 和变量 number 各自保存一份数值,修改 x 不影响 number。
再看数组:
public static void changeArray(int[] a) { a[0] = 10; } int[] arr = {1, 2, 3}; changeArray(arr); System.out.println(arr[0]); // 10 形参 a 得到了 arr 中引用的副本,二者仍指向同一个数组,所以 a[0] = 10 会改变外部看到的元素。
如果只是让形参指向新数组,外部变量不会跟着改变:
public static void replaceArray(int[] a) { a = new int[]{7, 8, 9}; } int[] arr = {1, 2, 3}; replaceArray(arr); System.out.println(arr[0]); // 1 记住:a[i] = ... 修改数组元素;a = ... 修改形参自己的引用值。 传递数组时也不需要复制整组元素。
3.2 作为返回值:返回一组计算结果
方法的返回类型写成 int[],就可以返回一个整型数组的引用。
例如,生成斐波那契数列的前 n 项:前两项为 1、1,从第三项开始,每项等于前两项之和。
public static int[] fib(int n) { if (n <= 0) { return null; } int[] result = new int[n]; result[0] = 1; if (n >= 2) { result[1] = 1; } for (int i = 2; i < n; i++) { result[i] = result[i - 1] + result[i - 2]; } return result; } 调用:
int[] result = fib(6); if (result != null) { System.out.println(Arrays.toString(result)); // [1, 1, 2, 3, 5, 8] } -
n == 1时只能访问result[0],不能直接给result[1]赋值。 - 本例约定
n <= 0返回null,调用方使用前先判断。 - 方法结束后,局部变量会离开作用域,但返回的数组仍被调用方引用,因此还能使用。
- 本例使用
int,只适合结果未超出int范围的项数;从第 47 项开始会溢出。
4. 数组拷贝:创建一份独立的数组
4.1 Arrays.copyOf 与 Arrays.copyOfRange
b = a 只复制引用。如果需要独立修改一个 int 数组,可以创建新数组并复制元素:
int[] a = {10, 20, 30, 40}; int[] b = Arrays.copyOf(a, a.length); b[0] = 99; System.out.println(Arrays.toString(a)); // [10, 20, 30, 40] System.out.println(Arrays.toString(b)); // [99, 20, 30, 40] Arrays.copyOf(原数组, 新长度) 的第二个参数是新数组的长度。新长度较小时截取前面的元素,较大时多出来的 int 元素为 0;原数组的长度不变。
复制部分元素可以使用:
int[] part = Arrays.copyOfRange(a, 1, 3); System.out.println(Arrays.toString(part)); // [20, 30] 范围是 [起始下标, 结束下标),左闭右开。因此 [1, 3) 复制下标 1、2,不包含下标 3。
4.2 用循环实现拷贝
public static int[] copyOf(int[] arr) { int[] result = new int[arr.length]; for (int i = 0; i < arr.length; i++) { result[i] = arr[i]; } return result; } 关键是先 new 一个数组,再逐个复制元素;如果直接 return arr,返回的仍是原数组的引用。
以上“修改互不影响”针对 int[] 这样的基本类型数组。若元素本身是引用,复制元素时复制的仍是引用,涉及的对象可能继续共享;这属于后续的深浅拷贝问题,本章不展开。
5. 用数组练习查找、排序与逆序
以下方法均假定传入的数组不是 null;长度为 0 的数组可以正常处理。
5.1 顺序查找:从头逐个比较
不要求数组有序。找到目标就返回下标,全部检查后仍没找到则返回 -1。
public static int find(int[] arr, int target) { for (int i = 0; i < arr.length; i++) { if (arr[i] == target) { return i; } } return -1; } int[] arr = {1, 2, 3, 10, 5, 6}; System.out.println(find(arr, 10)); // 3 System.out.println(find(arr, 8)); // -1 -1 不是合法下标,适合表示未找到。它必须放在循环结束之后:当前元素不相等,不代表后面也没有。若目标出现多次,这个方法返回第一次出现的位置。
5.2 二分查找:每次排除一半范围
前提:数组已经有序。下面的代码只适用于升序数组。 升序指元素从小到大排列,允许相等;降序则相反。
用 left、right 表示待查找范围的左右边界,两端都包含在范围内。取中间下标 mid:
- 目标小于
arr[mid]:目标只可能在左半边,令right = mid - 1。 - 目标大于
arr[mid]:目标只可能在右半边,令left = mid + 1。 - 相等:返回
mid。
public static int binarySearch(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (target < arr[mid]) { right = mid - 1; } else if (target > arr[mid]) { left = mid + 1; } else { return mid; } } return -1; } 例如,在 {1, 2, 3, 4, 5, 6} 中查找 6:
| 轮次 | left | right | mid | 比较与下一步 |
|---|---|---|---|---|
| 1 | 0 | 5 | 2 | 6 > 3,令 left = 3 |
| 2 | 3 | 5 | 4 | 6 > 5,令 left = 5 |
| 3 | 5 | 5 | 5 | 6 == 6,返回下标 5 |
三个边界要理解:
-
left == right时还有一个元素没检查,所以循环条件是<=。 - 中间元素已经比较过,下一轮必须排除它,因此使用
mid - 1或mid + 1。 -
left > right表示范围为空,才确定未找到。
left + (right - left) / 2 能避免直接计算 left + right 可能造成的整数溢出。每轮约缩小一半范围,对 10000 个元素最多约需比较 14 次。若有重复目标值,这个实现不保证返回第一次出现的位置。
5.3 冒泡排序:把较大的元素逐步换到右边
按升序排序时,从左到右比较相邻元素;前面的更大就交换。一趟结束后,尚未排好部分的最大值会到达该部分最右端。
以 {9, 5, 2, 7} 的第一趟为例:
[9, 5, 2, 7] 交换 9 和 5 → [5, 9, 2, 7] 交换 9 和 2 → [5, 2, 9, 7] 交换 9 和 7 → [5, 2, 7, 9] 9 已到最终位置,下一趟只需处理前 3 个元素;第二趟后得到 [2, 5, 7, 9]。
public static void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } -
i表示已经完成的趟数,最多需要长度 - 1趟。 - 每完成一趟,右侧多一个已就位的元素,因此内层范围减去
i。 - 内层要访问
arr[j + 1],所以条件为j < arr.length - 1 - i。 - 交换时用
temp暂存原值,避免赋值覆盖后丢失数据。 - 若要降序,将比较条件改为
arr[j] < arr[j + 1]。
调用后,原数组会直接改变:
int[] arr = {9, 5, 2, 7}; bubbleSort(arr); System.out.println(Arrays.toString(arr)); // [2, 5, 7, 9] 冒泡排序便于理解,但效率较低。需要排序时,可以使用工具方法:
int[] arr = {9, 5, 2, 7}; Arrays.sort(arr); // 直接把原 int 数组按升序排列 System.out.println(Arrays.toString(arr)); // [2, 5, 7, 9] Arrays.sort 的内部排序算法留到后续学习,本章掌握调用方式即可。
5.4 数组逆序:两端向中间交换
逆序是把原来的先后顺序颠倒,不等于降序排序。例如,{3, 1, 2} 逆序后是 {2, 1, 3}。
public static void reverse(int[] arr) { int left = 0; int right = arr.length - 1; while (left < right) { int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } 例如,{1, 2, 3, 4} 先交换首尾,得到 {4, 2, 3, 1},再交换中间两个元素,得到 {4, 3, 2, 1}。
每次交换后两端各向中间移动一步。相遇时是同一个元素,无需交换,因此条件为 left < right。
6. 二维数组:数组的元素也是数组引用
6.1 创建、访问与长度
二维数组可以按“行、列”理解,但本质是:外层数组的每个元素,保存着一个一维数组的引用。
int[][] a = new int[2][3]; // 2 行,每行 3 个 int,初始值均为 0 也可以直接给出数据:
int[][] a = { {1, 2, 3}, {4, 5, 6} }; 完整写法可以使用 new int[][]{{1, 2, 3}, {4, 5, 6}},但不能写成 new int[2][3]{{...}, {...}}。
System.out.println(a[0][1]); // 2:第 1 行、第 2 列 a[1][2] = 99; System.out.println(a[1][2]); // 99 System.out.println(a.length); // 2:外层长度,即行数 System.out.println(a[0].length); // 3:第 0 行的长度 a[row][col] 可以分两步看:先由 a[row] 找到这一行的数组,再用 [col] 取出该行中的元素。行下标和列下标都从 0 开始,都不能越界。
6.2 遍历二维数组
外层循环遍历行,内层循环遍历当前行的元素:
Related posts
Meet Lenso UI. Bringing the design language and interactions of iOS and iPadOS to the web. Glass materials. Elastic feedback. Fluid menus. Built with Base UI + StyleX. Try it: https://ui.lenso.dev

Governance Is Not Bureaucracy. It Is Demarcation.
How to check record counts and parent relationships after a Salesforce load
Parallel coding agents, one human: how to stop being the bottleneck
