🐸 青蛙编程网
首页 / Java / Java 基础 / 5. 数组

数组

这是什么:数组(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。

动手改

  1. 把数组改成 5 个成绩,算出平均分(提示:总分除以 scores.length)
  2. 找出数组里的最大值(提示:先假设第 1 个最大,挨个比过去)
  3. 故意取 scores[5] 跑一次,看看"越界"报错长什么样
  4. 用 Arrays.toString(scores) 打印整个数组,对比直接 println(scores) 的区别
  5. 用 Arrays.sort 把成绩从小到大排序后打印,再想想:怎么排出从大到小?
  6. 用增强 for 统计数组里超过 85 分的个数

下一章:方法,把代码打包复用 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。