第 06 课:数组、索引、遍历与常用算法
本课目标
学完后,你应该能够:
- 创建数组并理解数组长度固定、元素类型统一。
- 使用索引读取和修改元素。
- 使用普通
for和增强for遍历数组。 - 计算总和、平均值、最大值、最小值和满足条件的数量。
- 使用线性查找获得目标元素的索引。
- 理解数组是引用类型,以及直接赋值和真正复制的区别。
- 避免
ArrayIndexOutOfBoundsException和空指针问题。 - 把数组传给方法,并从方法返回一个新数组。
1. 为什么需要数组
如果要保存 5 个成绩,可以写 5 个变量:
int score1 = 88;
int score2 = 92;
int score3 = 76;
int score4 = 100;
int score5 = 65;
但这样很难用循环统一处理。数组把多个相同类型的值放在一个有序容器中:
int[] scores = {88, 92, 76, 100, 65};
可以把它想成一排编号盒子:
索引: 0 1 2 3 4
元素: 88 92 76 100 65
数组长度是 5,但最后一个索引是 4。Java 索引从 0 开始,因此合法范围永远是:
0 到 array.length - 1
2. 两种常见创建方式
先指定长度
int[] numbers = new int[3];
int[]:变量类型是“int 数组”。numbers:数组变量名。new int[3]:创建能保存 3 个整数的数组对象。- 创建后长度不能改变。
默认值:
| 元素类型 | 默认值 |
|---|---|
int、long 等整数 | 0 |
double 等小数 | 0.0 |
boolean | false |
char | \u0000,空字符 |
String 等引用类型 | null |
创建时直接提供元素
int[] scores = {88, 92, 76};
String[] names = {"小明", "小红", "小李"};
Java 根据元素数量确定长度。
推荐写法是 int[] scores,而不是虽然合法但不够直观的 int scores[]。
3. 读取、修改和 length
int[] scores = {88, 92, 76};
int first = scores[0]; // 读取第一个元素
scores[2] = 80; // 修改第三个元素
int size = scores.length;
数组的 length 是属性,不是方法,所以没有括号:
scores.length // 正确
scores.length() // 错误
最后一个元素通用写法:
scores[scores.length - 1]
4. 普通 for 遍历
for (int index = 0; index < scores.length; index++) {
System.out.println(scores[index]);
}
为什么条件是 < scores.length,而不是 <=?
长度为 5 时合法索引是 0–4。当 index == 5,scores[5] 已越界。使用 < length 正好在第一个非法索引前停止。
普通 for 适用于:
- 需要索引;
- 需要修改某个位置;
- 只遍历部分范围;
- 需要同时访问前后元素。
5. 增强 for 遍历
for (int score : scores) {
System.out.println(score);
}
读作:“对 scores 中的每个 int 元素,临时命名为 score。”
增强 for 适合从头到尾只读取值。它不直接提供索引。
下面不会修改原数组:
for (int score : scores) {
score = 0;
}
这里改变的只是局部变量 score。要修改原数组,使用索引:
for (int index = 0; index < scores.length; index++) {
scores[index] = 0;
}
6. 数组是引用类型
int[] first = {10, 20, 30};
int[] second = first;
second[0] = 99;
此时 first[0] 也会变成 99。second = first 复制的是对同一个数组对象的引用,不是元素副本:
first ──┐
├──> [99, 20, 30]
second ──┘
创建独立副本:
int[] copied = Arrays.copyOf(first, first.length);
现在修改 copied 不会影响 first。
7. 数组常用统计算法
总和与平均值
int total = 0;
for (int number : numbers) {
total += number;
}
double average = (double) total / numbers.length;
强制转换 (double) 避免整数除法丢失小数。
最大值
int maximum = numbers[0];
for (int index = 1; index < numbers.length; index++) {
if (numbers[index] > maximum) {
maximum = numbers[index];
}
}
必须使用数组中的真实元素初始化最大值。不要随意写 maximum = 0,因为数组可能全部是负数。例如 {-8, -3, -10} 的最大值应是 -3,不是不存在于数组中的 0。
循环从索引 1 开始,因为索引 0 已经用于初始化。
条件计数
int count = 0;
for (int number : numbers) {
if (number % 2 == 0) {
count++;
}
}
8. 线性查找
public static int findIndex(int[] numbers, int target) {
for (int index = 0; index < numbers.length; index++) {
if (numbers[index] == target) {
return index;
}
}
return -1;
}
执行过程:
- 从索引 0 开始比较。
- 找到目标后立即
return index,方法结束。 - 循环全部结束仍未返回,说明没有找到。
-1不可能是合法索引,因此用它表示“未找到”。
如果数组中有多个相同元素,这个方法返回第一次出现的位置。
9. null、空数组与短路保护
if (numbers == null || numbers.length == 0) {
return 0.0;
}
两种不同状态:
numbers == null:变量没有指向任何数组对象。numbers.length == 0:确实存在数组对象,但里面没有元素。
|| 具有短路特性。numbers == null 为真时,右侧 numbers.length 不执行,因此避免空指针异常。
10. 常用 Arrays 工具
需要先导入:
import java.util.Arrays;
常用操作:
Arrays.toString(numbers); // 转成便于阅读的字符串
Arrays.sort(numbers); // 原地升序排序,会修改原数组
Arrays.copyOf(numbers, numbers.length); // 创建副本
Arrays.equals(first, second); // 比较元素内容是否相同
直接打印数组变量通常得到类似 [I@1b6d3586 的类型和哈希信息,不是元素列表:
System.out.println(numbers); // 不适合查看元素
System.out.println(Arrays.toString(numbers)); // 正确查看元素
11. 完整源码一:ArrayBasics.java
import java.util.Arrays;
public class ArrayBasics {
public static void main(String[] args) {
// new int[3] 创建长度固定为 3 的 int 数组,默认元素为 0。
int[] defaultNumbers = new int[3];
System.out.println("默认值:" + Arrays.toString(defaultNumbers));
int[] scores = {88, 92, 76, 100, 65};
System.out.println("第一个成绩:" + scores[0]);
System.out.println("最后一个成绩:" + scores[scores.length - 1]);
System.out.println("数组长度:" + scores.length);
scores[2] = 80;
for (int index = 0; index < scores.length; index++) {
System.out.println("scores[" + index + "] = " + scores[index]);
}
int total = 0;
for (int score : scores) {
total += score;
}
double average = (double) total / scores.length;
System.out.println("总分:" + total);
System.out.println("平均分:" + average);
int[] sameArray = scores;
sameArray[0] = 90;
System.out.println("sameArray 修改后,scores[0]:" + scores[0]);
int[] copiedArray = Arrays.copyOf(scores, scores.length);
copiedArray[0] = 60;
System.out.println("copiedArray 修改后,scores[0]:" + scores[0]);
System.out.println("原数组:" + Arrays.toString(scores));
System.out.println("复制数组:" + Arrays.toString(copiedArray));
}
}
执行主线:创建默认数组 → 创建成绩数组 → 读写索引 → 两种遍历 → 统计 → 演示共享引用 → 演示真正复制。重点观察 sameArray[0] 会影响 scores[0],但 copiedArray[0] 不会。
12. 完整源码二:ScoreAnalyzer.java
import java.util.Arrays;
import java.util.Scanner;
public class ScoreAnalyzer {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入成绩数量:");
int count = scanner.nextInt();
if (count <= 0) {
System.out.println("成绩数量必须大于 0。");
scanner.close();
return;
}
int[] scores = new int[count];
for (int index = 0; index < scores.length; index++) {
System.out.print("请输入第 " + (index + 1) + " 个成绩:");
scores[index] = scanner.nextInt();
}
int total = calculateTotal(scores);
double average = calculateAverage(scores);
int highest = findMaximum(scores);
int lowest = findMinimum(scores);
int passedCount = countPassed(scores);
System.out.println();
System.out.println("===== 成绩分析 =====");
System.out.println("全部成绩:" + Arrays.toString(scores));
System.out.println("总分:" + total);
System.out.println("平均分:" + average);
System.out.println("最高分:" + highest);
System.out.println("最低分:" + lowest);
System.out.println("及格人数:" + passedCount);
System.out.print("请输入要查找的成绩:");
int target = scanner.nextInt();
int foundIndex = findFirstIndex(scores, target);
if (foundIndex == -1) {
System.out.println("没有找到成绩 " + target);
} else {
System.out.println("第一次出现的位置是索引 " + foundIndex
+ ",也就是第 " + (foundIndex + 1) + " 个元素。");
}
scanner.close();
}
public static int calculateTotal(int[] numbers) {
int total = 0;
for (int number : numbers) {
total += number;
}
return total;
}
public static double calculateAverage(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return 0.0;
}
return (double) calculateTotal(numbers) / numbers.length;
}
public static int findMaximum(int[] numbers) {
int maximum = numbers[0];
for (int index = 1; index < numbers.length; index++) {
if (numbers[index] > maximum) {
maximum = numbers[index];
}
}
return maximum;
}
public static int findMinimum(int[] numbers) {
int minimum = numbers[0];
for (int index = 1; index < numbers.length; index++) {
if (numbers[index] < minimum) {
minimum = numbers[index];
}
}
return minimum;
}
public static int countPassed(int[] scores) {
int count = 0;
for (int score : scores) {
if (score >= 60) {
count++;
}
}
return count;
}
public static int findFirstIndex(int[] numbers, int target) {
for (int index = 0; index < numbers.length; index++) {
if (numbers[index] == target) {
return index;
}
}
return -1;
}
}
ScoreAnalyzer 流程详解
- 读取
count,先拒绝 0 和负数,保证后面numbers[0]安全。 new int[count]按运行时输入创建数组。- 输入循环中的
index + 1只用于给用户显示“第几个”;真正数组索引仍是index。 - 将统计逻辑拆进方法,
main负责组织数据流。 - 最大值和最小值从第一个真实元素开始,正确处理全负数。
- 查找结果为
-1时表示不存在,否则把零基索引转换成用户习惯的“第几个”。
13. 独立练习完整答案:ArrayPractice.java
练习目标:对 {12, 5, 8, 20, 7, 8} 求和、平均值、最大值、偶数数量、目标索引,并生成一个反转后的新数组。
import java.util.Arrays;
public class ArrayPractice {
public static void main(String[] args) {
int[] numbers = {12, 5, 8, 20, 7, 8};
System.out.println("原数组:" + Arrays.toString(numbers));
System.out.println("总和:" + calculateSum(numbers));
System.out.println("平均值:" + calculateAverage(numbers));
System.out.println("最大值:" + findMaximum(numbers));
System.out.println("偶数个数:" + countEvenNumbers(numbers));
System.out.println("数字 8 首次出现的索引:" + findIndex(numbers, 8));
int[] reversed = reverse(numbers);
System.out.println("反转数组:" + Arrays.toString(reversed));
System.out.println("原数组仍然是:" + Arrays.toString(numbers));
}
public static int calculateSum(int[] numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
public static double calculateAverage(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return 0.0;
}
return (double) calculateSum(numbers) / numbers.length;
}
public static int findMaximum(int[] numbers) {
int maximum = numbers[0];
for (int index = 1; index < numbers.length; index++) {
if (numbers[index] > maximum) {
maximum = numbers[index];
}
}
return maximum;
}
public static int countEvenNumbers(int[] numbers) {
int count = 0;
for (int number : numbers) {
if (number % 2 == 0) {
count++;
}
}
return count;
}
public static int findIndex(int[] numbers, int target) {
for (int index = 0; index < numbers.length; index++) {
if (numbers[index] == target) {
return index;
}
}
return -1;
}
public static int[] reverse(int[] numbers) {
int[] result = new int[numbers.length];
for (int index = 0; index < numbers.length; index++) {
int reversedIndex = numbers.length - 1 - index;
result[reversedIndex] = numbers[index];
}
return result;
}
}
反转算法逐轮推演
长度为 6 时,目标索引公式是 6 - 1 - index:
原索引 index | 原值 | 新索引 5-index |
|---|---|---|
| 0 | 12 | 5 |
| 1 | 5 | 4 |
| 2 | 8 | 3 |
| 3 | 20 | 2 |
| 4 | 7 | 1 |
| 5 | 8 | 0 |
最终新数组是 {8, 7, 20, 8, 5, 12}。方法创建新数组,因此原数组保持不变。
14. 常见错误
数组越界
int[] numbers = {10, 20, 30};
System.out.println(numbers[3]);
长度是 3,合法索引只有 0、1、2。访问索引 3 会抛出 ArrayIndexOutOfBoundsException。
空数组直接取第一个元素
int[] numbers = {};
int maximum = numbers[0];
空数组没有索引 0。调用最大值方法前必须保证数组非空,或由方法明确处理空数组。
对 null 读取 length
int[] numbers = null;
System.out.println(numbers.length);
会抛出 NullPointerException。可以使用短路校验:
if (numbers != null && numbers.length > 0) {
// 安全使用
}
误以为赋值会复制数组
second = first 只复制引用。需要独立副本时使用 Arrays.copyOf()。
15. 检查题与答案
问题
- 长度为 5 的数组,合法索引范围是什么?
array.length为什么不写括号?- 普通
for和增强for分别适合什么场景? - 为什么最大值不能总是初始化为 0?
- 查找方法返回
-1通常表示什么? int[] b = a;会复制数组元素吗?numbers == null || numbers.length == 0如何利用短路避免空指针?- 增强
for中执行number = 0,为什么通常不会修改原int[]? Arrays.sort(numbers)是否会修改原数组?- 长度为 6 时,索引 2 对应的反转索引是多少?
答案
0到4,也就是0到length - 1。length是数组属性,不是方法。- 需要索引、修改元素或控制范围时用普通
for;只需依次读取所有元素时用增强for。 - 如果所有元素都是负数,0 并不在数组中,却会被错误当成最大值。应使用第一个元素初始化。
- 没有找到目标,因为
-1不是合法数组索引。 - 不会,只会让
a和b指向同一个数组对象。 - 左侧为
true时,||右侧不执行,因此不会对null读取length。 number是保存当前基本类型值的局部副本,修改它不会写回数组元素。- 会,它进行原地排序。若要保留原顺序,应先复制再排序。
6 - 1 - 2 = 3。
16. 本课完成标准
- 能创建、读取、修改和遍历数组。
- 能解释索引范围为什么是
0到length - 1。 - 能独立写出总和、最大值、计数和线性查找。
- 能区分共享引用与复制数组。
- 能使用短路逻辑安全处理
null和空数组。 - 能看懂并推演反转数组的索引公式。
下一课学习 String:不可变性、常用方法、内容比较、字符串拆分,以及高效拼接用的 StringBuilder。