数组
这是什么:数组(Array) = 一排编了号、类型相同的格子,用一个名字就能整批管理一批数据。
为什么用:变量一次只能装一个值,要存全班 40 个成绩怎么办?总不能写成 score1、score2……score40。数组让你能说「3 号柜改成 95 分」,也能喊一句「从 1 号柜挨个检查到最后一号」。
如何用:本章先学声明与初始化,再练遍历、修改元素,最后写一个求最值与平均分的实战模板。
声明与初始化
public class ArrayDemo {
public static void main(String[] args) {
// 写法一:直接给值(最常用)
int[] scores = {90, 85, 77};
// 写法二:先定长度,元素默认值是 0
int[] temps = new int[3];
System.out.println(scores[0]); // 下标从 0 开始!
System.out.println(scores.length); // 注意:length 没有括号
System.out.println(temps[0]); // new 出来的默认值是 0
}
}
输出:
90
3
0
| 写法 | 什么时候用 | 特点 |
|---|---|---|
int[] a = {90, 85, 77}; |
一开始就知道有哪些值 | 长度由大括号里的个数决定 |
int[] a = new int[3]; |
先定长度、稍后再填值 | 元素先拿默认值顶着 |
写法二的"默认值"值得单独列表,它决定了"没赋值就取"会发生什么:
| 元素类型 | 默认值 |
|---|---|
int / byte / short / long |
0 |
double / float |
0.0 |
boolean |
false |
char |
空字符(显示为乱码方块) |
String 等对象类型 |
null |
下标从 0 开始:第 1 个元素是 scores[0],第 3 个是 scores[2]。取 scores[3] 会直接报错——只有 3 个格子,最大下标是 2。"数组越界"新手必踩,踩一次记一辈子。
为什么从 0 数起?因为下标本质是偏移量:第 1 个元素就在数组开头,偏移 0 个位置;第 n 个元素偏移 n-1 个位置。硬件按"开头地址 + 偏移"定位元素,从 0 数起最省事——这也是数组能"一步到位随机访问"的原因:想去第 100 个,直接算地址,不用从头数。
遍历:挨个处理
数组 + for 循环是黄金搭档:
public class ArrayLoop {
public static void main(String[] args) {
int[] scores = {90, 85, 77};
int sum = 0;
for (int i = 0; i < scores.length; i++) {
System.out.println("第 " + (i + 1) + " 个成绩:" + scores[i]);
sum += scores[i];
}
System.out.println("总分:" + sum);
// 想快速看数组内容?一行搞定
System.out.println(java.util.Arrays.toString(scores));
}
}
输出:
第 1 个成绩:90
第 2 个成绩:85
第 3 个成绩:77
总分:252
[90, 85, 77]
注意 i < scores.length 这里的小于号:下标最大只到 length - 1,写成 <= 必越界。这套"从 0 到 length-1"的循环模板占了数组操作的半壁江山,值得练到闭眼能敲。
除了普通 for,还有更简洁的增强 for(for-each),不碰下标、直接拿元素:
public class ForEachDemo {
public static void main(String[] args) {
int[] scores = {90, 85, 77};
for (int s : scores) { // 读作:对 scores 里的每个 s……
System.out.println("成绩:" + s);
}
}
}
输出:
成绩:90
成绩:85
成绩:77
| 遍历方式 | 写法 | 什么时候用 |
|---|---|---|
| 普通 for | for (int i = 0; i < a.length; i++) |
需要下标(定位第几个、按下标修改) |
| 增强 for | for (int s : a) |
只想逐个看每个元素,代码更短更安全 |
一个实用细节:打印 "第 " + (i + 1) + " 个" 时,i + 1 必须加括号——不加括号它会先和前面的字符串拼接,打出"第 01 个"这种怪话。
两个易混点
| 写法 | 含义 |
|---|---|
scores.length |
数组的长度,是属性,没有括号 |
"hello".length() |
字符串的长度,是方法,有括号 |
scores[0] |
第 1 个元素(下标从 0 开始) |
Arrays.toString(数组) |
把数组变成 [90, 85, 77] 这种可读形式 |
直接 System.out.println(scores) 打印出来的是 [I@1b6d3586 这样的天书(数组的内存地址)——想看内容,用 Arrays.toString。
[I@1b6d3586 怎么读:[ 表示这是数组,I 表示元素是 int,@ 后面是它在内存中的地址编号。Java 在提醒你:"数组本体不直接给你看,给你看的是地址。"
修改数组元素
int[] scores = {90, 85, 77};
scores[1] = 100; // 把第 2 个改成 100
System.out.println(scores[1]); // 100
数组是"可变容器"——长度改不了,格子里的值随便换。想"删除"中间一个元素?没有现成操作,只能后面的元素整体往前挪一位;想"插入"?同理往后挪。这类折腾正是数组的软肋,也是面向对象篇 ArrayList 登场的伏笔。
求最值与平均分:实战模板
"找最大值"是数组最经典的算法入门题,背下这个模板:
public class MaxDemo {
public static void main(String[] args) {
int[] scores = {90, 85, 77};
int max = scores[0]; // 先假设第 1 个最大
int sum = 0;
for (int i = 1; i < scores.length; i++) {
if (scores[i] > max) { // 来了更强的挑战者
max = scores[i]; // 擂主易位
}
sum += scores[i];
}
double avg = (double) sum / scores.length; // 强转避免整数除法
System.out.println("最高分:" + max);
System.out.println("平均分:" + avg);
}
}
输出:
最高分:90
平均分:84.0
两个细节值得嚼一嚼:擂主初始化用 scores[0] 而不是 0——如果全是负数,从 0 起步就永远找不出真正的最大值。平均分计算里 (double) sum 这个强转也不能少:两个 int 相除会先砍掉小数(变量章讲过的整数除法),先转成 double 再除才能得到带小数的结果。
常见数组操作速查
| 操作 | 写法 | 说明 |
|---|---|---|
| 取第 n 个 | scores[n-1] |
下标从 0 数起 |
| 长度 | scores.length |
属性,无括号 |
| 修改 | scores[i] = 值 |
下标必须在范围内 |
| 遍历 | for (int i = 0; i < scores.length; i++) |
下标遍历 |
| 快速打印 | Arrays.toString(scores) |
需 import java.util.Arrays; |
| 排序 | Arrays.sort(scores) |
从小到大,原地排序 |
| 填充 | Arrays.fill(scores, 0) |
所有格子填同一个值 |
| 复制 | Arrays.copyOf(scores, 5) |
复制成长度 5 的新数组 |
Arrays 是 Java 自带的数组工具箱,排序一行搞定:
import java.util.Arrays;
public class SortDemo {
public static void main(String[] args) {
int[] scores = {77, 90, 85};
Arrays.sort(scores); // 从小到大排好
System.out.println(Arrays.toString(scores));
}
}
输出:
[77, 85, 90]
❓ 常见问题
Q1:数组长度能改吗?
不能。int[] scores = {90, 85, 77} 长度固定是 3。需要会变长的容器,用面向对象篇学的 ArrayList。
Q2:scores.length 和 "hello".length() 为什么一个有括号一个没有?
length 是数组的属性(field),直接访问;length() 是 String 的方法,必须加括号调用。这是 Java 的设计,记住即可。
Q3:System.out.println(scores) 打印出天书怎么破?
数组对象直接打印会显示内存地址,不是内容。要看内容,用 Arrays.toString(scores)。
Q4:数组能装不同类型的东西吗?
不能,这是数组的立身之本:一列格子同类型,每个格子大小一致,才能按"下标 × 格子大小"瞬间定位。想混装整数和文字,用面向对象篇的集合容器。
Q5:二维数组是什么?
"数组里套数组"——`int[][] grid = {{1, 2}, {3, 4}};`,像表格一样行套列,访问用 grid[行][列]。入门阶段知道有这东西即可,实际开发里更常用"列表套列表"替代它。
Q6:数组下标可以是变量吗?
可以,这正是数组的威力所在:scores[i] 里的 i 就是变量,循环每转一圈它就指向下一个格子。数组 + 循环变量 = 批量处理的钥匙,本章所有遍历示例都是这么工作的。
Q7:为什么我 int[] a = new int[0]; 不报错?
长度为 0 的"空数组"完全合法,a.length 是 0,任何下标取值都会越界。它常用来表示"暂时没有数据"——比 null 安全,因为对空数组遍历只是什么都不干,对 null 遍历却会直接崩。
⚠️ 新手翻车现场
翻车一:数组越界
int[] scores = {90, 85, 77};
System.out.println(scores[3]); // ❌ ArrayIndexOutOfBoundsException
完整报错:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
Index 3 out of bounds for length 3
问题:只有 3 个元素,有效下标是 0~2,下标 3 已经越界。报错信息把"你要的下标"和"实际长度"都报出来了,很好读。
修正:访问前检查 i < scores.length。
翻车二:忘了初始化就取值
int[] temps = new int[3];
System.out.println(temps[0]); // ✅ 可以,默认是 0
String[] names = new String[3];
System.out.println(names[0]); // ✅ 可以,默认是 null
new 出来的数组,元素有默认值:int 型是 0,boolean 是 false,引用类型(String 等)是 null。取默认值不会报错,但用 null 再调方法会报 NullPointerException。
翻车三:length 加了括号
scores.length() // ❌ 编译报错,数组没有 length() 方法
问题:数组长度用 .length,没有括号。
修正:scores.length。
翻车四:循环条件写成 <=
int[] scores = {90, 85, 77};
for (int i = 0; i <= scores.length; i++) { // ❌ i 会走到 3
System.out.println(scores[i]); // 最后一轮越界
}
问题:前三个元素都正常打印,最后一轮才炸——这种"跑着跑着崩"比"一上来就崩"更迷惑人。
修正:条件改成 i < scores.length。口诀:数组循环,小于不带等号。
翻车五:只用 {90, 85, 77} 给"先声明的变量"赋值
int[] scores;
scores = {90, 85, 77}; // ❌ 编译报错
问题:大括号简写只能在声明的同时用,事后单独赋值不认。
修正:scores = new int[]{90, 85, 77};
🎯 什么时候用数组
- 固定数量、同类型的数据集合
- 需要按下标快速随机访问元素
- 数量已知且不会频繁增删
数量不固定、要频繁增删时,用面向对象篇学的 ArrayList。两者放一起对比更清楚:
| 维度 | 数组 | ArrayList |
|---|---|---|
| 长度 | 出生即定,终身不变 | 随时增删,自动伸缩 |
| 元素类型 | 只能是同一种 | 泛型约束,同一种 |
| 语法风格 | a[0]、a.length |
list.get(0)、list.size() |
| 打印内容 | 要 Arrays.toString |
直接 println 就是内容 |
| 适合场景 | 数量固定、追求直接 | 数量多变、增删频繁 |
📝 本章小结
- 数组是固定长度、同类型的容器,下标从 0 起,越界会报错
length是数组属性,没有括号;length()是 String 方法,有括号- 直接打印数组对象显示内存地址,看内容用
Arrays.toString() new出来的数组元素有默认值(int 是 0,String 是 null)- 遍历两板斧:要下标用普通 for,只取值用增强 for;循环条件用
<不用<= - 求最值模板:擂主先设
scores[0],挨个挑战赛 - 记忆口诀:下标从零起,length 不带括号,打印内容找 Arrays.toString。
记忆锚点:数组是一排带编号的储物柜——编号(下标)从 0 起跳,柜子数量(length)出厂焊死,Arrays 是管理处(排序、打印、复制找它),想塞进更多东西就得换一排更大的柜子(新建数组)或者升级成会伸缩的 ArrayList。
动手改
- 把数组改成 5 个成绩,算出平均分(提示:总分除以
scores.length) - 找出数组里的最大值(提示:先假设第 1 个最大,挨个比过去)
- 故意取
scores[5]跑一次,看看"越界"报错长什么样 - 用
Arrays.toString(scores)打印整个数组,对比直接println(scores)的区别 - 用
Arrays.sort把成绩从小到大排序后打印,再想想:怎么排出从大到小? - 用增强 for 统计数组里超过 85 分的个数
下一章:方法,把代码打包复用 🐸