Java 数组完全指南:从基础到实战
为什么需要数组?
假设要存储 5 个学生的 JavaSE 成绩,不用数组会怎样?
java
public class TestStudent {
public static void main(String[] args) {
int score1 = 70;
int score2 = 80;
int score3 = 85;
int score4 = 60;
int score5 = 90;
System.out.println(score1);
System.out.println(score2);
System.out.println(score3);
System.out.println(score4);
System.out.println(score5);
}
}
这段代码有三个问题:
- 学生数量一旦增加,变量数量就得同步增加------20 个学生要写 20 个变量,100 个学生要写 100 个
- 这些成绩类型完全一样,却要反复声明变量
- 无法批量处理,每个变量都得单独操作
数组就是为了解决这类问题。数组是相同类型元素的集合,在内存中占据一段连续空间。
一、数组的核心概念
1.1 数组的内存模型

数组在内存中有两个关键特征:
- 连续存储:所有元素在内存中紧挨着,没有间隙
- 编号访问:每个位置都有编号(下标),从 0 开始计数
1.2 如何创建数组
创建数组的基本格式:
java
T[] 数组名 = new T[N];
这三个部分的含义:
- T:元素类型,比如 int、double、String
- T\[\]:数组类型,表示"T 类型的数组"
- N:数组长度,能放几个元素
实际例子:
java
int[] array1 = new int[10]; // 能放 10 个整数
double[] array2 = new double[5]; // 能放 5 个小数
String[] array3 = new String[3]; // 能放 3 个字符串
二、数组的初始化
初始化就是给数组填入初始数据。Java 提供了两种方式。
2.1 动态初始化
创建时只指定长度,不指定内容:
java
int[] array = new int[10];
这时数组已经存在,每个位置都有默认值(int 类型默认是 0)。
2.2 静态初始化
创建时直接填入数据:
java
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hello", "Java", "!!!"};
可以简写(省略 new T[]):
java
int[] array1 = {1,2,3,4,5,6,7,8,9};
double[] array2 = {1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = {"hello", "Java", "!!!"};
⚠️ 注意事项:
-
静态初始化时编译器会自动计算长度 ,不需要手动指定
[]里的数字 -
{}中的数据类型必须一致,都要是声明的类型 -
简写形式只能在声明时使用,不能拆成两行:
java
// ✅ 正确
int[] array = {1, 2, 3};
// ❌ 错误
int[] array;
array = {1, 2, 3}; // 编译失败
- 不推荐 C 风格写法:
java
// 不推荐:容易让人误以为 arr 的类型是 int
int arr[] = {1, 2, 3};
// 推荐:类型清晰
int[] arr = {1, 2, 3};
- 动态和静态初始化都可以分两步,但简写不行:
java
// ✅ 动态初始化可以分步
int[] array1;
array1 = new int[10];
// ✅ 静态初始化可以分步
int[] array2;
array2 = new int[]{10, 20, 30};
// ❌ 简写形式不能分步
int[] array3;
array3 = {1, 2, 3}; // 编译失败
2.3 数组的默认值
如果用动态初始化创建数组,每个位置会自动填入默认值:
| 类型 | 默认值 |
|---|---|
| byte | 0 |
| short | 0 |
| int | 0 |
| long | 0 |
| float | 0.0f |
| double | 0 |
| char | ' '(空字符) |
| boolean | false |
引用类型(如 String)默认值是 null:
java
String[] words = new String[3];
// words 的三个元素都是 null
三、数组的基本使用
3.1 访问数组元素
数组在内存中连续存储,每个位置都有编号(下标)。下标从 0 开始,依次递增。通过下标可以读取或修改任意位置的元素。
java
int[] array = new int[]{10, 20, 30, 40, 50};
System.out.println(array[0]); // 10
System.out.println(array[1]); // 20
System.out.println(array[2]); // 30
System.out.println(array[3]); // 40
System.out.println(array[4]); // 50
// 修改元素
array[0] = 100;
System.out.println(array[0]); // 100
⚠️ 下标越界问题:
数组下标的有效范围是 [0, N-1],其中 N 是数组长度。访问范围之外的下标会抛出异常:
java
int[] array = {1, 2, 3};
System.out.println(array[3]); // 越界!数组只有 0、1、2 三个位置
// 运行结果
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
3.2 遍历数组
遍历是指依次访问数组中的每一个元素。
最直接的方法是逐个打印:
java
int[] array = new int[]{10, 20, 30, 40, 50};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[4]);
但这样写有问题:
- 数组增加一个元素,就得加一行代码
- 100 个元素就要写 100 行
- 修改操作(比如把打印改成加 1)也要改 100 行
既然操作都一样,就可以用循环。
方式一:用 for 循环
java
int[] array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
array.length 可以获取数组长度,这样就不用手动数有几个元素了。
方式二:用 for-each 循环
java
int[] array = {1, 2, 3};
for (int x : array) {
System.out.println(x);
}
for-each 是 for 循环的另一种写法,代码更简洁,也不容易写错循环条件。
四、数组是引用类型
4.1 JVM 内存分布
程序运行时,数据需要存储在内存中。JVM 把内存划分成不同区域:

JVM 内存主要分为 5 个区域:
1. 方法区(Method Area)
- 存什么:类的信息、常量、静态变量
- 白话:存放类的"设计图纸",比如类有哪些字段、哪些方法
2. 虚拟机栈(VM Stack)⭐ 重点
- 存什么:方法里的局部变量
- 白话:方法在执行时,把变量临时放这里。方法结束,变量就被清掉了
- 举例:
int a = 10;这个 a 就在栈里
3. 本地方法栈(Native Stack)
- 存什么:调用 C/C++ 代码时用的数据
- 白话:Java 调用系统底层功能时用,日常开发很少接触
4. 堆(Heap)⭐ 重点
- 存什么:new 出来的所有对象和数组
- 白话:所有用 new 创建的东西都放这里,只要有引用指着就不会被清理
- 举例:
int[] arr = new int[10];这个数组就在堆里
5. 程序计数器(PC Register)
- 存什么:记录代码执行到哪一行了
- 白话:类似书签,记住下一步该执行哪条指令
4.2 基本类型 vs 引用类型
基本类型的变量直接存储值:
java
int a = 10;
变量 a 里存的就是 10。
引用类型的变量存储的是地址:
java
int[] array = new int[]{1,2,3};
变量 array 里存的不是数组本身,而是数组在堆中的地址。通过这个地址,才能找到真正的数组。

4.3 引用变量的赋值
java
public static void func() {
int[] array1 = new int[3];
array1[0] = 10;
array1[1] = 20;
array1[2] = 30;
int[] array2 = new int[]{1,2,3,4,5};
array2[0] = 100;
array2[1] = 200;
array1 = array2; // 把 array2 中保存的数组地址赋给 array1
array1[2] = 300;
array1[3] = 400;
array2[4] = 500;
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
}
执行 array1 = array2 后,array1 和 array2 都指向同一个数组。这时通过 array1 或 array2 修改数据,另一个也会看到变化。

这种赋值方式叫浅拷贝(或引用拷贝),只复制地址,不复制数组内容。
4.4 认识 null
null 表示"空引用",也就是不指向任何对象。
java
int[] arr = null;
System.out.println(arr[0]);
// 运行结果
// Exception in thread "main" java.lang.NullPointerException
null 类似 C 语言的 NULL(空指针),表示无效的内存位置。不能对 null 进行读写操作,否则会抛出 NullPointerException。
五、数组的应用场景
5.1 保存数据
java
public static void main(String[] args) {
int[] array = {1, 2, 3};
for(int i = 0; i < array.length; i++) {
System.out.println(array[i] + " ");
}
}
5.2 作为函数参数
基本类型作为参数
java
public static void main(String[] args) {
int num = 0;
func(num);
System.out.println("num = " + num);
}
public static void func(int x) {
x = 10;
System.out.println("x = " + x);
}
// 运行结果
// x = 10
// num = 0
在 func 中修改 x,不会影响 num。
数组作为参数
java
public static void main(String[] args) {
int[] arr = {1, 2, 3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
// 运行结果
// a[0] = 10
// arr[0] = 10
在 func 中修改数组,方法外的数组也会变化。
原因:数组是引用类型,传递的是地址,不是整个数组的拷贝。这样可以避免大数组的复制开销。
5.3 作为函数返回值
比如获取斐波那契数列的前 N 项:
java
public class TestArray {
public static int[] fib(int n) {
if(n <= 0) {
return null;
}
int[] array = new int[n];
array[0] = array[1] = 1;
for(int i = 2; i < n; i++) {
array[i] = array[i-1] + array[i-2];
}
return array;
}
public static void main(String[] args) {
int[] array = fib(10);
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
六、操作数组的工具类 Arrays
Java 提供了 java.util.Arrays 类,包含常用的数组操作方法。
6.1 数组转字符串
java
import java.util.Arrays;
int[] arr = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
System.out.println(newArr);
// 运行结果
// [1, 2, 3, 4, 5, 6]
打印数组变得更方便。
6.2 数组拷贝
java
import java.util.Arrays;
public static void func() {
// newArr 和 arr 指向同一个数组
// newArr 修改后,arr 也会变
int[] arr = {1,2,3,4,5,6};
int[] newArr = arr;
newArr[0] = 10;
System.out.println("arr: " + Arrays.toString(arr));
// 使用 copyOf 创建新数组
// arr 和 newArr 是两个独立的数组
arr[0] = 1;
newArr = Arrays.copyOf(arr, arr.length);
System.out.println("newArr: " + Arrays.toString(newArr));
// 修改 arr 不会影响 newArr
arr[0] = 10;
System.out.println("arr: " + Arrays.toString(arr));
System.out.println("newArr: " + Arrays.toString(newArr));
// 拷贝指定范围
int[] newArr2 = Arrays.copyOfRange(arr, 2, 4);
System.out.println("newArr2: " + Arrays.toString(newArr2));
}
⚠️ 注意:如果数组存储的是引用类型,拷贝时需要考虑深浅拷贝问题(后续会详细介绍)。
自己实现拷贝:
java
public static int[] copyOf(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i];
}
return ret;
}
6.3 查找元素(顺序查找)
在数组中查找指定元素,返回位置:
java
public static void main(String[] args) {
int[] arr = {1,2,3,10,5,6};
System.out.println(find(arr, 10));
}
public static int find(int[] arr, int data) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == data) {
return i;
}
}
return -1; // 没找到返回 -1
}
// 运行结果
// 3
6.4 查找元素(二分查找)
如果数组已经排序,可以用更快的二分查找。
什么是排序数组?
- 升序:1 2 3 4,依次递增
- 降序:4 3 2 1,依次递减
二分查找的思路:
- 取中间元素,与目标比较
- 如果相等,找到了
- 如果目标更小,去左半边继续查找
- 如果目标更大,去右半边继续查找
java
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(binarySearch(arr, 6));
}
public static int binarySearch(int[] arr, int toFind) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (toFind < arr[mid]) {
right = mid - 1; // 去左边找
} else if (toFind > arr[mid]) {
left = mid + 1; // 去右边找
} else {
return mid; // 找到了
}
}
return -1; // 没找到
}
// 运行结果
// 5
对于 10000 个元素的数组,二分查找最多只需要 14 次比较。数组越大,优势越明显。
6.5 数组排序(冒泡排序)
将数组按升序或降序排列。
算法思路(以升序为例):
- 从前往后比较相邻的两个元素
- 如果前面的比后面的大,就交换它们
- 这样一轮下来,最大的元素就到了最后
- 重复这个过程,直到所有元素都排好序
java
public static void main(String[] args) {
int[] arr = {9, 5, 2, 7};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 1; j < arr.length-i; j++) {
if (arr[j-1] > arr[j]) {
int tmp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = tmp;
}
}
}
}
// 运行结果
// [2, 5, 7, 9]
冒泡排序比较慢。Java 内置了更高效的排序算法:
java
public static void main(String[] args) {
int[] arr = {9, 5, 2, 7};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
6.6 数组逆序
将数组元素反转。
思路:
- 设置两个下标,一个指向开头,一个指向结尾
- 交换两个位置的元素
- 两个下标向中间移动,继续交换
- 直到它们相遇
java
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4};
reverse(arr);
System.out.println(Arrays.toString(arr));
}
public static void reverse(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
七、二维数组
7.1 普通二维数组
二维数组本质是一维数组,只不过每个元素又是一个一维数组。
创建语法:
java
数据类型[][] 数组名称 = new 数据类型[行数][列数] { 初始化数据 };
行数不能省略,列数可以省略。
示例:
java
int[][] arr = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int row = 0; row < arr.length; row++) {
for (int col = 0; col < arr[row].length; col++) {
System.out.printf("%d\t", arr[row][col]);
}
System.out.println("");
}
// 运行结果
// 1 2 3 4
// 5 6 7 8
// 9 10 11 12
理解二维数组:
arr.length是行数arr[row].length是第 row 行的列数- 每一行是独立的一维数组

7.2 不规则的二维数组
二维数组的每一行长度可以不同:
java
int[][] array = new int[2][];
array[0] = new int[3]; // 第 1 行有 3 列
array[1] = new int[5]; // 第 2 行有 5 列
这就是不规则的二维数组。
八、实战案例:学生成绩管理系统
综合运用数组的各种操作。
需求:
- 录入学生成绩
- 计算平均分
- 查找最高分和最低分
- 按成绩降序排列
- 查询指定学生成绩
java
import java.util.Arrays;
import java.util.Scanner;
public class StudentScoreManager {
// 学生姓名数组
private static String[] names;
// 学生成绩数组
private static int[] scores;
// 当前学生数量
private static int count = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入班级学生人数:");
int capacity = scanner.nextInt();
names = new String[capacity];
scores = new int[capacity];
while (true) {
printMenu();
System.out.print("请选择操作:");
int choice = scanner.nextInt();
switch (choice) {
case 1:
inputScores(scanner);
break;
case 2:
calculateAverage();
break;
case 3:
findMaxMin();
break;
case 4:
sortByScore();
break;
case 5:
searchStudent(scanner);
break;
case 6:
displayAllScores();
break;
case 0:
System.out.println("感谢使用,再见!");
return;
default:
System.out.println("无效选择,请重新输入");
}
}
}
// 打印菜单
private static void printMenu() {
System.out.println("\n========== 学生成绩管理系统 ==========");
System.out.println("1. 录入学生成绩");
System.out.println("2. 计算平均分");
System.out.println("3. 查找最高分和最低分");
System.out.println("4. 按成绩降序排列");
System.out.println("5. 查询指定学生成绩");
System.out.println("6. 显示所有学生成绩");
System.out.println("0. 退出系统");
System.out.println("=====================================");
}
// 1. 录入学生成绩
private static void inputScores(Scanner scanner) {
if (count >= names.length) {
System.out.println("学生人数已满,无法继续录入!");
return;
}
System.out.print("请输入学生姓名:");
String name = scanner.next();
System.out.print("请输入学生成绩(0-100):");
int score = scanner.nextInt();
if (score < 0 || score > 100) {
System.out.println("成绩无效,请输入 0-100 之间的分数");
return;
}
names[count] = name;
scores[count] = score;
count++;
System.out.println("录入成功!");
}
// 2. 计算平均分
private static void calculateAverage() {
if (count == 0) {
System.out.println("暂无学生数据!");
return;
}
int sum = 0;
for (int i = 0; i < count; i++) {
sum += scores[i];
}
double average = (double) sum / count;
System.out.printf("班级平均分:%.2f\n", average);
}
// 3. 查找最高分和最低分
private static void findMaxMin() {
if (count == 0) {
System.out.println("暂无学生数据!");
return;
}
int maxScore = scores[0];
int minScore = scores[0];
String maxName = names[0];
String minName = names[0];
for (int i = 1; i < count; i++) {
if (scores[i] > maxScore) {
maxScore = scores[i];
maxName = names[i];
}
if (scores[i] < minScore) {
minScore = scores[i];
minName = names[i];
}
}
System.out.println("最高分:" + maxName + " - " + maxScore + "分");
System.out.println("最低分:" + minName + " - " + minScore + "分");
}
// 4. 按成绩降序排列
private static void sortByScore() {
if (count == 0) {
System.out.println("暂无学生数据!");
return;
}
// 冒泡排序(降序)
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - 1 - i; j++) {
if (scores[j] < scores[j + 1]) {
// 交换成绩
int tempScore = scores[j];
scores[j] = scores[j + 1];
scores[j + 1] = tempScore;
// 交换姓名
String tempName = names[j];
names[j] = names[j + 1];
names[j + 1] = tempName;
}
}
}
System.out.println("排序完成!");
displayAllScores();
}
// 5. 查询指定学生成绩
private static void searchStudent(Scanner scanner) {
if (count == 0) {
System.out.println("暂无学生数据!");
return;
}
System.out.print("请输入要查询的学生姓名:");
String name = scanner.next();
for (int i = 0; i < count; i++) {
if (names[i].equals(name)) {
System.out.println("学生:" + names[i] + ",成绩:" + scores[i] + "分");
return;
}
}
System.out.println("未找到该学生!");
}
// 6. 显示所有学生成绩
private static void displayAllScores() {
if (count == 0) {
System.out.println("暂无学生数据!");
return;
}
System.out.println("\n========== 学生成绩列表 ==========");
System.out.println("序号\t姓名\t\t成绩");
System.out.println("----------------------------------");
for (int i = 0; i < count; i++) {
System.out.printf("%d\t%s\t\t%d\n", i + 1, names[i], scores[i]);
}
System.out.println("==================================\n");
}
}
运行示例:
请输入班级学生人数:5
========== 学生成绩管理系统 ==========
1. 录入学生成绩
2. 计算平均分
3. 查找最高分和最低分
4. 按成绩降序排列
5. 查询指定学生成绩
6. 显示所有学生成绩
0. 退出系统
=====================================
请选择操作:1
请输入学生姓名:张三
请输入学生成绩(0-100):85
录入成功!
请选择操作:1
请输入学生姓名:李四
请输入学生成绩(0-100):92
录入成功!
请选择操作:2
班级平均分:88.50
请选择操作:3
最高分:李四 - 92分
最低分:张三 - 85分
九、常见问题与注意事项
9.1 数组下标越界
java
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // ArrayIndexOutOfBoundsException
原因:数组下标范围是 0, length-1,arr3 超出范围
解决 :使用 arr.length 动态获取长度,确保下标在有效范围内
9.2 空指针异常
java
int[] arr = null;
System.out.println(arr[0]); // NullPointerException
原因:数组引用是 null,没有指向任何对象
解决:使用前先检查:
java
if (arr != null && arr.length > 0) {
System.out.println(arr[0]);
}
9.3 浅拷贝 vs 深拷贝
java
// 浅拷贝:只复制引用
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // arr1 和 arr2 指向同一个数组
arr2[0] = 100;
System.out.println(arr1[0]); // 输出 100
// 深拷贝:创建新数组
int[] arr3 = Arrays.copyOf(arr1, arr1.length);
arr3[0] = 200;
System.out.println(arr1[0]); // 输出 100
9.4 数组长度固定
数组创建后长度不能改变。如果需要动态扩容,应该使用 ArrayList(后续课程介绍)。
java
int[] arr = new int[3];
// 无法直接扩展到 5 个元素,只能创建新数组并拷贝
int[] newArr = new int[5];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
十、总结
数组的本质
- 相同类型元素的集合
- 在内存中占据连续空间
- 通过下标快速访问(时间复杂度 O(1))
数组的优缺点
✅ 优点:
- 访问速度快,支持随机访问
- 内存连续,缓存友好
- 实现简单,适合固定数量的数据
❌ 缺点:
- 长度固定,无法动态扩容
- 插入删除效率低(需要移动元素)
- 创建时必须指定大小
关键要点
- 数组下标从 0 开始,范围是 0, length-1
- 数组是引用类型,存储在堆中
- 使用
Arrays工具类可以简化操作 - 注意避免下标越界和空指针异常
- 理解浅拷贝和深拷贝的区别
进阶方向
- 学习 ArrayList 等动态数据结构
- 掌握各种排序算法(快排、归并等)
- 了解二维数组在图形、矩阵中的应用
- 深入理解 JVM 内存管理机制
数组是编程基础中的基础。掌握数组操作对后续学习至关重要。继续练习,在实际项目中灵活运用!
参考资料: