Java 数组基础与常见操作
这是 Java 入门系列的第五天。前四天我们的变量一次只能装一个值,今天学习第一个”容器”——数组:一次声明就能管理一批同类型数据,它也是后面所有集合框架的底层基石。
本篇目标
- 理解数组的内存模型:堆中连续空间 + 栈中引用
- 掌握数组的三种定义与初始化方式
- 独立完成遍历、取最值、反转、查找等基本操作
- 会用 Arrays 工具类,理解二维数组”数组的数组”本质
- 讲清 Java 参数传递为什么”基本类型不改、数组会改”
为什么需要数组
要存 50 个学生的成绩,定义 50 个 score1…score50 吗?显然不行——无法批量处理,也应对不了”人数不定”的情况。数组解决的就是这个问题:用一块连续的内存,存储同一种数据类型的多个值,通过下标统一访问。数组既能装基本类型,也能装引用类型。
数组的内存模型
先补一点 JVM 内存常识(入门阶段记住两块即可):
- 栈:存放局部变量(方法里声明的变量),方法结束即弹栈销毁。
- 堆:存放所有
new出来的东西,由 JVM 管理,没有任何引用指向时才被垃圾回收。
看一行代码:
int[] arr = new int[3]; |
这行代码实际发生了两件事:在堆中开辟一块能容纳 3 个 int 的连续空间(每个 int 占 4 字节,共 12 字节),并自动填上默认值;在栈中声明引用变量 arr,把堆中那块空间的首地址存进去。arr 不是数组本身,而是指向数组的”遥控器”。
下标为什么从 0 开始? 因为下标的本质是偏移量。已知首地址,第 i 个元素的地址 = 首地址 + i × 每个元素的字节数。从 0 开始,arr[0] 的偏移就是 0,公式干干净净,一次乘法加一次加法就能定位任意元素——这正是数组随机访问是 O(1) 的原因。如果从 1 开始,每次寻址都得多做一次减 1 的修正,白白浪费。
动态初始化的默认值:整数类型默认 0,浮点默认 0.0,boolean 默认 false,char 默认 '\u0000',引用类型默认 null。直接打印 arr 会看到 [I@1b6d3586 这样的东西:[ 表示数组,I 表示 int 型,@ 后面是哈希值的十六进制(可近似看作地址标识)。
引用的别名效应:
int[] a = {1, 2, 3}; |
b = a 复制的不是数组内容,而是地址——栈上两个变量,堆上只有一个数组。
三种定义与初始化
int[] a = new int[3]; // 1. 动态初始化:指定长度,系统赋默认值 |
两个注意点:不能”动静结合”,new int[3]{1,2,3} 是语法错误;简写版不能拆成 int[] c; c = {1,2,3}; 两行。元素个数在编译期未知时用动态初始化(如长度来自用户输入),内容已知时用静态初始化。
两个经典异常
int[] arr = {1, 2, 3}; |
合法下标永远是 0 ~ arr.length - 1;空指针则是”引用还在、数组没了”。排查思路:先看异常类型,再看报错行号,回到代码检查下标来源和引用是否被置空。
四个基本操作
public class ArrayOps { |
反转的循环条件是 i < arr.length / 2——走到中点就够了,再走一圈就换回去了。查找约定俗成返回 -1 表示没找到,因为 -1 不可能是合法下标。
Arrays:官方工具类
手写一遍是为了理解原理,开发中直接用 java.util.Arrays:
import java.util.Arrays; |
最容易踩的坑是 binarySearch:二分查找要求数组已经排好序,对无序数组调用它,结果是未定义的——要么先 sort,要么别用。
二维数组:数组的数组
Java 里没有真正的”矩阵”,二维数组的本质是一个元素为”一维数组引用”的一维数组:
int[][] arr = new int[3][2]; |
内存上,外层数组在堆中占 3 个槽,每个槽里存的不是数字,而是一个指向内层 int 数组的地址。正因为内层是独立的对象,二维数组的每一行长度可以不一样:
int[][] a = new int[3][]; // 只确定行数,每行暂时是 null |
遍历就是两层”外行内列”的循环:外循环拿到每一行,内循环遍历行内元素。
public class QuarterSales { |
注意内层条件是 sales[i].length——每行长度可能不同,写死长度正是越界异常的温床。
参数传递:为什么基本类型不改、数组会改
public class ParamPassing { |
Java 的参数传递永远是值传递,没有例外。区别只在于”值”是什么:基本类型传的是数据本身的拷贝,方法里改的只是一份副本,方法弹栈后无影无踪;数组传的是地址的拷贝,方法内外两个引用指向堆中同一个数组,所以方法里对元素的修改在方法返回后依然可见。把这个想通,就拿到了理解一切引用类型行为的总钥匙。
小结
- 数组是堆中的连续空间,栈上的变量只是引用;下标从 0 开始是因为下标即偏移量,换来 O(1) 寻址。
- 初始化三种写法:动态给长度、静态给内容、静态简写;不能动静结合。
- 越界和空指针是数组的两大经典异常,合法下标是
0 ~ length - 1。 - 遍历、最值、反转、查找要能默写;开发中打印、排序、查找交给 Arrays,binarySearch 前先排序。
- 二维数组是”数组的数组”,各行可以不等长,内层循环条件用
arr[i].length。 - 参数传递永远是值传递:基本类型传数据副本,数组传地址副本。

