主题
界面
主题
数组是 Java 里第一个容器:把多个同类型的数据组织在一起,用下标访问。
它也是第一个引用类型容器——数组变量里存的不是数据本身,而是数据在内存中的地址。这一点决定了数组的赋值、传参行为,为后面学集合与对象打好基础。
完成本节后,你应该能够:
length 与下标的关系;new int[3] 的元素是 0,而 new String[3] 的元素是 null;Arrays 工具类完成排序、查找、复制、输出;int[] alias = numbers; 之后修改 alias 为什么会影响 numbers;只有一个成绩时用 int score 就够了;有 50 个成绩时:
int[] scores = new int[50]?一行解决,配合循环轻松求和。数组带来的不只是"少写变量",而是可循环处理——这是从"写几句代码"到"处理数据"的分水岭。
同时,数组的局限(长度固定)也直接解释了为什么需要集合(《List、Set、Map》)。
int[] a = new int[3]; // 只定长度,元素为默认值 [0, 0, 0]
int[] b = {1, 2, 3}; // 声明时直接初始化
int[] c = new int[]{4, 5, 6}; // 完整写法,可用于重新赋值推荐把 [] 写在类型后面(int[] a),而不是变量名后面(int a[])——前者一眼能看出"类型是 int 数组"。
int[] numbers = {5, 2, 9, 1, 7};
// 下标: 0 1 2 3 4
// numbers.length == 5三条必须记住的规则:
length - 1;length 是属性(不带括号),字符串是 length() 方法(带括号)——这两个经常被搞混;ArrayIndexOutOfBoundsException,这是运行时异常,编译器不会提前发现。new int[3] 里是 0 int[] numbers = new int[3]; // [0, 0, 0]
String[] names = new String[2]; // [null, null]数组在创建时,元素会被自动初始化为类型的默认值(基本类型为 0 / false,引用类型为 null)。这与成员变量的规则一致(《变量与数据类型》)。
实用推论:拿到一个 String[] 就直接调 .length() 会 NPE——因为元素可能是 null:
String[] names = new String[2];
System.out.println(names[0].length()); // NullPointerException栈 堆
numbers ────────────────▶ [5, 2, 9, 1, 7]
alias ────────────────▶ (同一个数组对象)int[] numbers = {5, 2, 9, 1, 7};
int[] alias = numbers; // 复制的是地址,不是数组
alias[0] = 100; // 通过 alias 修改
System.out.println(numbers[0]); // 100 —— numbers 也变了这与 《类与对象》 里对象引用的行为完全一致。"变量里存的是地址"这个认知,是理解 Java 传参和对象共享的关键。
int[] numbers = {5, 2, 9, 1, 7};
// 增强 for:只读遍历,代码简洁
for (int value : numbers) {
System.out.print(value + " ");
}
// 下标 for:需要下标或需要修改元素时
for (int i = 0; i < numbers.length; i++) {
numbers[i] = numbers[i] * 2;
}选择标准:只读用增强 for;要下标或要改元素用下标 for。
数组本身没有多少方法,常用功能都在 java.util.Arrays 里:
| 方法 | 作用 | 注意 |
|---|---|---|
Arrays.toString(arr) | 打印数组内容 | 直接 println(arr) 只会打印地址 |
Arrays.sort(arr) | 排序 | 就地修改,返回 void |
Arrays.binarySearch(arr, key) | 二分查找 | 必须先排序,返回下标或负数 |
Arrays.copyOf(arr, newLength) | 复制/扩容 | 长度可大于原数组(补默认值) |
Arrays.equals(a, b) | 比较内容 | 数组的 == 比的是地址 |
Arrays.deepToString(arr) | 打印二维数组 | 二维数组必须用它 |
binarySearch 找不到时的返回值是 -(插入点) - 1,所以可能是负数——这不是错误码,而是"如果插入应该放哪"的提示。
int[] scores = {90, 85, 77};
int total = 0;
for (int score : scores) {
total += score;
}
System.out.println("平均分: " + (double) total / scores.length);int[] numbers = {5, 2, 9, 1, 7};
Arrays.sort(numbers); // [1, 2, 5, 7, 9]
int index = Arrays.binarySearch(numbers, 9); // 4
int missing = Arrays.binarySearch(numbers, 100); // -6(不存在)-6 的含义:如果要把 100 插入,应该放在下标 5 的位置,而返回值是 -(5) - 1 = -6。判断存在性时要用 >= 0,不能判断"是否等于 -1"。
int[][] matrix = {{1, 2}, {3, 4}};
int[][] shallow = matrix.clone(); // 只复制了"外层数组",内层数组仍是同一个
shallow[0][0] = 99;
System.out.println(matrix[0][0]); // 99 —— 原数组也变了结论:二维数组的 clone() 与 Arrays.copyOf() 都是浅拷贝。需要真正独立时,要逐行复制:
int[][] deep = new int[matrix.length][];
for (int i = 0; i < matrix.length; i++) {
deep[i] = Arrays.copyOf(matrix[i], matrix[i].length);
}文件:labs/java-syntax/src/main/java/cn/kb/learn/syntax/ArraysLesson.java
package cn.kb.learn.syntax;
import java.util.Arrays;
public class ArraysLesson {
public static void main(String[] args) {
// 1. 声明、初始化与访问
int[] numbers = {5, 2, 9, 1, 7};
System.out.println("长度: " + numbers.length);
System.out.println("首尾元素: " + numbers[0] + " / " + numbers[numbers.length - 1]);
// 2. 排序与输出
Arrays.sort(numbers);
System.out.println("排序后: " + Arrays.toString(numbers));
// 3. 二分查找(前提:数组已排序)
System.out.println("9 的下标: " + Arrays.binarySearch(numbers, 9));
System.out.println("不存在的值返回负数: " + Arrays.binarySearch(numbers, 100));
// 4. 复制与扩容
int[] firstThree = Arrays.copyOf(numbers, 3);
System.out.println("前三个元素: " + Arrays.toString(firstThree));
// 5. 聚合运算
System.out.println("求和: " + sum(numbers));
System.out.println("最大值: " + max(numbers));
// 6. 二维数组
int[][] grid = {{1, 2, 3}, {4, 5, 6}};
System.out.println("二维数组: " + Arrays.deepToString(grid));
System.out.println("grid[1][2] = " + grid[1][2]);
// 7. 数组是引用类型:两个变量指向同一个对象
int[] alias = numbers;
alias[0] = 100;
System.out.println("通过 alias 修改后 numbers[0] = " + numbers[0]);
// 8. 数组的默认值
int[] defaults = new int[3];
System.out.println("new int[3] 的默认值: " + Arrays.toString(defaults));
String[] strings = new String[2];
System.out.println("new String[2] 的默认值: " + Arrays.toString(strings));
}
static int sum(int[] values) {
int total = 0;
for (int value : values) {
total += value;
}
return total;
}
static int max(int[] values) {
if (values == null || values.length == 0) {
throw new IllegalArgumentException("数组不能为空");
}
int result = values[0];
for (int value : values) {
if (value > result) {
result = value;
}
}
return result;
}
}运行命令:
cd labs
./mvnw -pl java-syntax compile
java -cp java-syntax/target/classes cn.kb.learn.syntax.ArraysLesson预期输出:
长度: 5
首尾元素: 5 / 7
排序后: [1, 2, 5, 7, 9]
9 的下标: 4
不存在的值返回负数: -6
前三个元素: [1, 2, 5]
求和: 24
最大值: 9
二维数组: [[1, 2, 3], [4, 5, 6]]
grid[1][2] = 6
通过 alias 修改后 numbers[0] = 100
new int[3] 的默认值: [0, 0, 0]
new String[2] 的默认值: [null, null]int[] alias = numbers; 处断点,展开 Variables 面板看 numbers 与 alias 的 id 是否相同——它们指向同一个数组对象;alias[0] = 100; 执行后,观察 numbers[0] 也变成 100;Arrays.sort(numbers) 的数组改成 {5, 2, 9, 1, 7} 后手动断点逐步执行,观察就地排序(原数组被修改,没有新数组产生);numbers[5](越界),用异常断点(《Java 程序的运行与调试》)停在抛异常的位置。length 与 length() int[] arr = {1, 2};
System.out.println(arr.length); // 对:属性
System.out.println("abc".length()); // 对:方法
System.out.println(arr.length()); // 编译错误记忆:数组是"属性",字符串是"方法"。
int[] arr = {1, 2, 3};
System.out.println(arr); // [I@1b6d3586
System.out.println(Arrays.toString(arr)); // [1, 2, 3]二维数组同理,要用 Arrays.deepToString。
int[] arr = {1, 2, 3};
for (int i = 0; i <= arr.length; i++) { // <= 导致越界
System.out.println(arr[i]);
}修复:数组遍历统一用 <;需要"最后一个元素"时写 arr[arr.length - 1]。
= 复制了数组 int[] a = {1, 2, 3};
int[] b = a; // 不是复制,是别名
b[0] = 99;
System.out.println(a[0]); // 99修复:要真正独立用 Arrays.copyOf(a, a.length)。
目标:手写三个数组算法,并用 Arrays 工具类验证。
步骤:
-6 这个返回值);int[] reverse(int[] source):返回反转后的新数组(不修改原数组);int indexOf(int[] values, int target):返回目标值的下标,找不到返回 -1;Arrays.sort + Arrays.binarySearch 重写第 3 步,对比两者的前提条件与性能差异(提示:二分查找要求有序);验收方式:能说出"为什么 binarySearch 必须先排序",以及不排序时会得到什么结果(可能返回错误的负数)。
扩展任务:实现 int[][] transpose(int[][] matrix)(矩阵转置),注意二维数组的行列关系,并用 Arrays.deepToString 验证结果。
基础题
int[] a = new int[5]; 之后 a[0] 是 0。(对 / 错)println(arr) 打印出来的是 [I@1b6d3586 这样的内容?代码阅读题
int[] a = {1, 2, 3};
int[] b = Arrays.copyOf(a, 5);
b[0] = 100;
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));b 的后两个元素是什么?编程题
int secondMax(int[] values),返回数组中第二大的数(要求:不能用 Arrays.sort,数组长度至少为 2)。综合任务
int[] scores(0-100 的整数);提示 1(方向):第 5 题维护两个变量 max 与 second,遍历时按"比 max 大"和"介于两者之间"两种情况更新。
提示 2(关键点):第 6 题的分数段统计可以用一个长度为 5 的 int[],用 score / 10 计算下标(注意 100 分要单独处理或取 Math.min(score / 10, 9))。
提示 3(接近答案):第 6 步的越界处理用 continue 跳过,但记得在跳过前打印提示。
length、length()。println 打印的是 toString() 的结果,而数组没有重写 toString(),于是打印"类型标记 + 哈希码"。[I 表示"int 数组",@ 后面是对象地址的十六进制表示。[1, 2, 3]
[100, 2, 3, 0, 0]Arrays.copyOf(a, 5) 会创建一个长度为 5 的新数组:前 3 个元素复制自 a,多出来的两个补默认值 0。修改 b[0] 不影响 a——这证明 copyOf 是真正的复制(与 int[] b = a 的别名行为不同)。
package cn.kb.learn.syntax;
public class SecondMax {
static int secondMax(int[] values) {
if (values == null || values.length < 2) {
throw new IllegalArgumentException("数组长度至少为 2");
}
int max = Integer.MIN_VALUE;
int second = Integer.MIN_VALUE;
for (int value : values) {
if (value > max) {
second = max; // 原最大值降级为第二大
max = value;
} else if (value > second && value != max) {
second = value;
}
}
if (second == Integer.MIN_VALUE) {
throw new IllegalArgumentException("不存在第二大的数(所有元素相同)");
}
return second;
}
public static void main(String[] args) {
System.out.println(secondMax(new int[]{3, 9, 9, 5, 1}));
System.out.println(secondMax(new int[]{1, 2}));
}
}预期输出:
5
1注意 else if 里的 value != max:它保证"重复的最大值"不会被当成第二大。
package cn.kb.learn.syntax;
public class ScoreAnalysis {
public static void main(String[] args) {
int[] scores = {95, 88, 72, 60, 45, 100, -5, 89, 77, 58};
int[] buckets = new int[5]; // 0-59, 60-69, 70-79, 80-89, 90-100
int valid = 0;
int passed = 0;
int total = 0;
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int score : scores) {
if (score < 0 || score > 100) {
System.out.println("跳过无效分数: " + score);
continue;
}
valid++;
total += score;
if (score >= 60) {
passed++;
}
if (score > max) {
max = score;
}
if (score < min) {
min = score;
}
buckets[Math.min(score / 10, 4)]++;
}
System.out.printf("有效人数: %d%n", valid);
System.out.printf("平均分: %.1f%n", (double) total / valid);
System.out.printf("最高 / 最低: %d / %d%n", max, min);
System.out.printf("及格率: %.1f%%%n", passed * 100.0 / valid);
System.out.printf("分数段: 0-59=%d 60-69=%d 70-79=%d 80-89=%d 90-100=%d%n",
buckets[0], buckets[1], buckets[2], buckets[3], buckets[4]);
}
}数组是怎么用来统计的:分数段只有 5 个且连续,用 score / 10 当下标直接定位到对应格子,把"多分支 if-else"变成了"一次数组下标访问"。这是数组最典型的用法——用下标代替分支。
预期输出:
跳过无效分数: -5
有效人数: 9
平均分: 76.0
最高 / 最低: 100 / 45
及格率: 88.9%
分数段: 0-59=2 60-69=1 70-79=2 80-89=2 90-100=2Arrays 的常用方法,尤其是 toString 与 binarySearch。《方法、参数与重载》:把重复的代码封装成方法,并搞清 Java 的参数传递到底传了什么。