【本节目标】
- 搞懂 Java 数组的本质,告别 C 语言数组的思维定式;
- 熟练掌握数组的创建、初始化、遍历等核心用法;
- 吃透数组作为引用类型的特性,搞定数组和方法的互操作;
- 会写数组常见练习题(拷贝、排序、逆序等),掌握 Java 内置数组工具类。
前言:Java 数组 vs C 语言数组(核心差异)
C 语言的数组是 "裸内存块",靠指针操作;Java 的数组是 "对象",有自己的属性(比如length),还受 JVM 内存管理保护 ------ 简单说:C 语言数组是 "毛坯房",Java 数组是 "精装修房",更安全、更易用,但用法差异要记牢!
1. 数组的基本概念
1.1 为什么要使用数组?
生活例子:你有 100 个同学的成绩要存,如果用score1、score2...score100这 100 个变量,写起来要疯;用数组score[100],一个变量就能存 100 个值,遍历、修改都方便。
核心作用:批量存储相同类型的数据,统一管理和操作,避免变量泛滥。
1.2 什么是数组?
Java 数组的官方定义:相同数据类型的元素组成的固定长度的有序集合。✅ 和 C 语言的相同点:
- 元素类型必须一致;
- 长度一旦确定,不能修改(都是 "定长");
- 下标从 0 开始。
C 语言数组 vs Java 数组 特性对比表
| 特性维度 | C 语言数组 | Java 数组 |
|---|---|---|
| 核心本质 | 底层是内存地址(指针),无封装 | 完整的对象(存储在堆区),自带属性和方法 |
| 长度获取方式 | 手动计算:sizeof(数组名)/sizeof(数组元素) |
直接调用属性:数组名.length(编译器维护) |
| 下标越界处理 | 不做检查(越界会导致内存乱码 / 程序崩溃) | 运行时严格检查(抛出 ArrayIndexOutOfBoundsException 异常) |
| 初始化语法 | 静态 / 动态初始化二选一,无简写形式 | 支持简写(如 int[] arr = {1,2,3};),编译器自动推导长度 |
| 内存分配 | 可栈区 / 堆区分配(栈区数组生命周期随作用域) | 固定在堆区分配(栈区仅存数组引用) |
| 元素类型限制 | 仅支持基本数据类型 | 支持基本类型、引用类型(对象数组) |
补充示例(更易理解差异)
1.3 数组的创建及初始化(重点对比 C 语言)
1.3.1 数组的创建
Java 创建数组的格式(两种常用):
// 格式1:指定类型和长度(动态初始化基础)
数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr1 = new int[5]; // 创建长度为5的int数组
// 格式2:C语言风格(Java兼容,但不推荐)
int arr2[] = new int[5]; // 效果同上,只是写法不同
和 C 语言的区别:
- C 语言:
int arr[5];(不用 new,直接在栈 / 堆创建); - Java:必须用
new(除非静态初始化简写),数组对象默认在堆区,不能直接int arr[5];(编译报错)。
1.3.2 数组的初始化(Java 更灵活!)
Java 数组初始化分两种,重点记和 C 语言的差异:
① 动态初始化:指定长度,不指定元素
// Java:指定长度,元素用默认值
int[] arr = new int[5]; // 5个int,默认值0
double[] arr2 = new double[3]; // 默认值0.0
boolean[] arr3 = new boolean[2]; // 默认值false
String[] arr4 = new String[4]; // 默认值null(不是空字符串!)
和 C 语言的区别:
- C 语言:动态初始化(比如
int *arr = malloc(5*sizeof(int));)需要手动分配内存,且不初始化时元素是随机值; - Java:动态初始化后,元素有默认值(不用手动清脏数据),且 JVM 自动管理内存,不用 free。
② 静态初始化:指定元素,不指定长度
// 完整写法
int[] arr1 = new int[]{1, 2, 3, 4};
// 简写(Java特有!C语言没有这种写法)
int[] arr2 = {1, 2, 3, 4};
// 错误写法(C语言能这么写?也不行!)
// int[] arr3 = new int[4]{1,2,3,4}; // Java编译报错:静态初始化不能同时指定长度
✅ 静态初始化注意事项:
-
编译器会根据
{}里的元素个数自动确定长度(比如{1,2,3}长度就是 3); -
{}里的元素类型必须和数组类型一致(比如int[] arr = {1, 2.0}编译报错); -
简写只能在声明时用,不能分开写:
// 错误 int[] arr; arr = {1,2,3}; // 编译报错!简写只能int[] arr = {1,2,3}; // 正确 arr = new int[]{1,2,3};
和 C 语言的区别:
- C 语言静态初始化:
int arr[] = {1,2,3};(可以),但int arr[4] = {1,2,3};(剩余元素补 0); - Java:静态初始化不能同时指定长度 (
new int[4]{1,2,3}报错),且简写只能在声明时用。
数组默认值总结(Java 特有,C 语言无):
| 数据类型 | 默认值 |
|---|---|
| 整型(byte/short/int/long) | 0 |
| 浮点型(float/double) | 0.0 |
| 布尔型(boolean) | false |
| 引用类型(String / 数组等) | null |
1.4 数组的使用
1.4.1 数组中元素访问
Java 访问数组元素:数组名[下标],和 C 语言写法一样,但有核心区别!
int[] arr = {10, 20, 30};
System.out.println(arr[0]); // 10(访问第一个元素)
arr[1] = 25; // 修改第二个元素为25
⚠️ 注意事项(重点避坑):
-
随机访问:数组是连续内存,通过下标能直接访问任意元素(和 C 语言一样);
-
下标越界 :Java 会严格检查!下标必须在
[0, 数组名.length)范围内,越界直接抛ArrayIndexOutOfBoundsException:int[] arr = new int[3];
// System.out.println(arr[3]); // 运行报错!下标3超出范围(0-2)
和 C 语言的区别:
C 语言数组越界不报错,会访问到内存中其他数据,导致程序崩溃 / 乱码;Java 直接抛异常,更安全。
1.4.2 遍历数组(三种方法,Java 比 C 语言多 2 种!)
遍历 = 把数组的每个元素都访问一遍,Java 有 3 种方式,C 语言主要用 for 循环:
① for 循环(和 C 语言类似)
int[] arr = {1, 2, 3, 4, 5};
// Java:用arr.length获取长度,不用手动算
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " "); // 输出1 2 3 4 5
}
和 C 语言的区别:
- C 语言:
for(int i=0; i<5; i++)(要手动写长度,或用 sizeof 计算); - Java:
arr.length直接获取,哪怕数组长度改了,循环不用动,更灵活。
② for-each 循环(增强 for 循环,Java 特有!C 语言没有)
int[] arr = {1, 2, 3, 4, 5};
// for(元素类型 变量名 : 数组名)
for (int x : arr) {
System.out.print(x + " "); // 输出1 2 3 4 5
}
✅ 优点:不用管下标,直接遍历所有元素,代码更简洁;
❌ 缺点:不能修改数组元素 (x 是临时变量,改 x 不会改数组),也不能跳过元素;
int[] arr = {1, 2, 3};
for (int x : arr) {
x *= 2; // 改的是临时变量x,数组元素不变
}
System.out.println(Arrays.toString(arr)); // 还是[1,2,3]
③ Arrays.toString ()(Java 工具类,一键打印,特有!)
C 语言要手动写循环打印数组,Java 直接调用Arrays工具类的方法,一行搞定:
import java.util.Arrays; // 必须导入!
public class ArrayDemo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
// 直接打印数组的字符串形式:[1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr));
}
}
和 C 语言的区别:
C 语言printf("%p", arr);打印的是数组地址;
Java 直接System.out.println(arr);也会打印地址(比如[I@1b6d3586),必须用**Arrays.toString()才会打印元素!**
2. 数组是引用类型(Java 核心,和 C 语言天差地别!)
2.1 先懂 JVM 的内存分布(简化版)
Java 内存分两块核心区域,数组的存储和这两块有关:
| 内存区域 | 存储内容 | 特点 |
|---|---|---|
| 栈区 | 基本类型变量、引用变量 | 方法执行完自动释放 |
| 堆区 | 数组对象、对象 | JVM 垃圾回收器自动回收 |
举个例子:int[] arr = new int[]{1,2,3};
- 栈区:存
arr(引用变量),里面装的是 "数组对象在堆区的地址"; - 堆区:存真正的数组元素
[1,2,3],还有数组的length属性(值为 3)。
和 C 语言的区别:
C 语言的数组名就是地址(指针),没有 "引用变量" 的概念;
Java 的数组名是 "引用",指向堆区的数组对象,不是地址本身。
2.2 基本类型变量 vs 引用类型变量
| 类型 | 存储内容 | 例子 |
|---|---|---|
| 基本类型 | 直接存值 | int a = 10;(栈区存 10) |
| 引用类型 | 存堆区对象的地址 | int[] arr = new int[3];(栈区存地址,堆区存数组) |
代码对比(一眼看懂):
// 基本类型:值存在栈区,修改互不影响
int a = 10;
int b = a;
b = 20;
System.out.println(a); // 10(a没变)
// 引用类型:arr1和arr2指向同一个堆区数组
int[] arr1 = new int[]{1,2,3};
int[] arr2 = arr1; // 拷贝的是地址,不是数组!
arr2[0] = 100;
System.out.println(Arrays.toString(arr1)); // [100,2,3](arr1也变了)
和 C 语言的区别:
C 语言int arr1[3] = {1,2,3}; int arr2[3] = arr1;直接报错(数组不能直接赋值);
Java 引用变量可以赋值,本质是拷贝地址。
2.3 两个引用指向同一对象(修改会互相影响)
int[] arr = {10, 20, 30};
int[] arrCopy = arr; // 两个引用指向同一个数组对象
arrCopy[1] = 200;
System.out.println(arr[1]); // 200(arr也被改了)
生活例子:
你和朋友都有一把钥匙(引用),开同一扇门(数组对象),朋友进门把灯关了(修改元素),你进门也会看到灯关了。
2.4 认识 null(Java 特有,C 语言没有)
null表示 "引用变量没有指向任何对象",相当于 "空钥匙",不能开任何门:
int[] arr = null; // arr不指向任何数组对象
// System.out.println(arr.length); // 运行报错:NullPointerException(空指针异常)
// System.out.println(arr[0]); // 同样报空指针
和 C 语言的区别:
C 语言用NULL(0)表示空指针,但 Java 的null不是 0,是专门表示引用为空的关键字,且空指针访问会抛异常,C 语言会崩溃。
3. 数组的应用场景
3.1 保存数据(最基础)
// 保存5个学生的成绩
double[] scores = {98.5, 90.0, 85.5, 95.0, 88.0};
// 遍历打印成绩
System.out.println("学生成绩:" + Arrays.toString(scores));
3.2 作为函数的参数(核心!对比 C 语言)
① 参数传基本数据类型(和 C 语言一样)
public static void changeNum(int x) {
x = 100; // 形参x是拷贝,改x不影响实参
}
public static void main(String[] args) {
int num = 10;
changeNum(num);
System.out.println(num); // 10(没变化)
}
结论:基本类型传参,形参是实参的拷贝,修改形参不影响实参(C/Java 都一样)。
② 参数传数组类型(引用类型,和 C 语言看似一样,本质不同)
public static void changeArray(int[] arr) {
arr[0] = 100; // 形参arr拷贝的是地址,指向堆区数组,修改会影响实参
}
public static void main(String[] args) {
int[] arr = {10, 20, 30};
changeArray(arr);
System.out.println(Arrays.toString(arr)); // [100,20,30](实参被改了)
}
✅ 结论:数组传参,传的是 "地址拷贝",形参和实参指向同一个堆区数组,修改数组元素会互相影响;
和 C 语言的区别:
- C 语言:数组传参本质是传指针,
void changeArray(int arr[])等价于void changeArray(int *arr); - Java:没有指针,传的是 "引用"(地址的封装),但效果和 C 语言一样(能改数组元素),只是 Java 更安全(越界会抛异常)。
💡 核心优势:数组传参只传地址,不用拷贝整个数组(数组长的话,拷贝开销大),C/Java 都利用了这个特性。
3.3 作为函数的返回值(Java 常用,C 语言麻烦)
C 语言返回数组需要手动管理内存(容易内存泄漏),Java 直接返回数组引用,安全又方便:
// 生成一个长度为n的数组,元素是1~n的随机数
public static int[] createRandomArray(int n) {
int[] arr = new int[n];
Random random = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(n) + 1;
}
return arr; // 返回数组引用
}
public static void main(String[] args) {
int[] randomArr = createRandomArray(5);
System.out.println("随机数组:" + Arrays.toString(randomArr));
}
和 C 语言的区别:
C 语言返回数组不能返回栈区数组(函数结束栈区释放),只能返回堆区数组(malloc 分配),还要手动 free;
Java 返回的是堆区数组引用,JVM 自动回收,不用管内存。
4. 数组练习(新手必写,附代码)
4.1 数组转字符串(模拟 Arrays.toString ())
public static String arrayToString(int[] arr) {
if (arr == null) {
return "null"; // 处理空数组
}
if (arr.length == 0) {
return "[]";
}
String str = "[";
for (int i = 0; i < arr.length; i++) {
str += arr[i];
if (i != arr.length - 1) {
str += ", ";
}
}
str += "]";
return str;
}
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arrayToString(arr)); // [1, 2, 3]
}
4.2 数组拷贝(Java 有 3 种方式,C 语言靠循环 /memcpy)
方式 1:手动循环拷贝
int[] arr = {1, 2, 3};
int[] copyArr = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
copyArr[i] = arr[i];
}
方式 2:Arrays.copyOf ()(Java 特有,可扩容)
import java.util.Arrays;
int[] arr = {1, 2, 3};
// 拷贝全部元素,长度和原数组一样
int[] copyArr1 = Arrays.copyOf(arr, arr.length);
// 拷贝并扩容(长度5,剩余元素补默认值0)
int[] copyArr2 = Arrays.copyOf(arr, 5);
System.out.println(Arrays.toString(copyArr2)); // [1,2,3,0,0]
方式 3:System.arraycopy ()(高效,底层 native 方法)
int[] arr = {1, 2, 3};
int[] copyArr = new int[3];
// System.arraycopy(原数组, 原数组起始下标, 目标数组, 目标数组起始下标, 拷贝长度)
System.arraycopy(arr, 0, copyArr, 0, arr.length);
和 C 语言的区别:
C 语言用memcpy(copyArr, arr, sizeof(arr)),
Java 不用关心内存大小,直接指定长度,更安全。
4.3 查找数组中指定元素(顺序查找)
// 找到返回下标,没找到返回-1
public static int findElement(int[] arr, int target) {
if (arr == null) {
return -1;
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] arr = {10, 20, 30, 40};
System.out.println(findElement(arr, 30)); // 2
System.out.println(findElement(arr, 50)); // -1
}
✅ 扩展:Java 内置Arrays.binarySearch()(二分查找,要求数组有序):
int[] arr = {10, 20, 30, 40};
// 二分查找30,返回下标2
int index = Arrays.binarySearch(arr, 30);
System.out.println(index); // 2
4.4 数组排序(冒泡排序 vs Java 内置排序)
① 冒泡排序(基础,性能低)
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 外层循环:控制排序轮数
for (int i = 0; i < arr.length - 1; i++) {
boolean flag = false; // 优化:标记是否交换过
// 内层循环:每轮比较相邻元素
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = true;
}
}
if (!flag) {
break; // 没交换,说明数组已有序,提前退出
}
}
}
public static void main(String[] args) {
int[] arr = {5, 3, 1, 4, 2};
bubbleSort(arr);
System.out.println(Arrays.toString(arr)); // [1,2,3,4,5]
}
② Java 内置排序(Arrays.sort (),高效)
int[] arr = {5, 3, 1, 4, 2};
Arrays.sort(arr); // 升序排序(Java默认升序)
System.out.println(Arrays.toString(arr)); // [1,2,3,4,5]
和 C 语言的区别:
C 语言要自己写排序 / 用 qsort,
Java 内置Arrays.sort(),底层是优化的快速排序,性能远高于冒泡。
4.5 数组逆序
public static void reverseArray(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int left = 0;
int right = arr.length - 1;
while (left < right) {
// 交换左右元素
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
reverseArray(arr);
System.out.println(Arrays.toString(arr)); // [5,4,3,2,1]
}
5. 二维数组(Java vs C 语言,差异巨大!)
Java 的二维数组:数组的数组 (本质是一维数组里装一维数组),C 语言的二维数组是 "连续的二维内存块"------ 简单说:Java 二维数组可以是 "不规则的",C 语言必须是 "规则的矩阵"。
5.1 二维数组的初始化(对比 C 语言)
① 静态初始化(Java 特有简写)
// 完整写法
int[][] arr1 = new int[][]{{1,2,3}, {4,5,6}};
// 简写(Java特有,C语言没有)
int[][] arr2 = {{1,2,3}, {4,5,6}};
② 动态初始化(规则二维数组)
// C语言:int arr[2][3];(连续内存)
// Java:创建2行3列的二维数组(每个一维数组长度都是3)
int[][] arr3 = new int[2][3];
③ 不规则二维数组(Java 特有!C 语言做不到)
Java 可以创建 "每行长度不同" 的二维数组,C 语言必须是规则矩阵:
// 创建2行,但列数先不指定(arr[0]、arr[1]都是null)
int[][] arr4 = new int[2][];
// 给第一行分配3列,第二行分配5列(不规则)
arr4[0] = new int[3];
arr4[1] = new int[5];
System.out.println(arr4[0].length); // 3
System.out.println(arr4[1].length); // 5
和 C 语言的区别:
C 语言int arr[2][];编译报错,必须指定列数;
Java 支持 "先指定行,后指定列",实现不规则二维数组。
5.2 二维数组的遍历(三种方法)
int[][] arr = {{1,2,3}, {4,5,6}};
// 方法1:双重for循环(和C语言类似)
for (int i = 0; i < arr.length; i++) { // 遍历行(arr.length是行数)
for (int j = 0; j < arr[i].length; j++) { // 遍历列(arr[i].length是第i行的列数)
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
// 方法2:for-each循环(Java特有)
for (int[] row : arr) { // 遍历每一行(row是一维数组)
for (int num : row) { // 遍历每行的元素
System.out.print(num + " ");
}
System.out.println();
}
// 方法3:Arrays.deepToString()(Java特有,一键打印)
System.out.println(Arrays.deepToString(arr)); // [[1,2,3], [4,5,6]]
和 C 语言的区别:
- C 语言:
for(int i=0; i<2; i++) for(int j=0; j<3; j++)(必须手动写行列数); - Java:
arr.length获取行数,arr[i].length获取第 i 行列数,支持不规则数组遍历。
5.3 二维数组的行与列(核心)
int[][] arr = {{1,2,3}, {4,5,6}};
System.out.println(arr.length); // 2(行数,即一维数组的长度)
System.out.println(arr[0].length); // 3(第一行的列数)
System.out.println(arr[1].length); // 3(第二行的列数)
✅ 结论:Java 二维数组就是 "特殊的一维数组"------ 每个元素都是一个一维数组。
5.4 空指针注意(不规则数组)
int[][] arr = new int[2][]; // 只指定行数,列数未指定
// System.out.println(arr[0].length); // 报错:NullPointerException(arr[0]是null)
// 解决:先给每行分配列数
arr[0] = new int[3];
System.out.println(arr[0].length); // 3(正常)
6. Arrays 工具类扩展(Java 特有,必背!)
除了前面的toString()、sort()、copyOf()、binarySearch(),还有这些常用方法:
import java.util.Arrays;
public class ArraysUtilDemo {
public static void main(String[] args) {
int[] arr1 = {1,2,3};
int[] arr2 = {1,2,3};
int[] arr3 = {1,2,4};
// 1. equals:比较两个数组是否完全一致(元素+顺序)
System.out.println(Arrays.equals(arr1, arr2)); // true
System.out.println(Arrays.equals(arr1, arr3)); // false
// 2. fill:填充数组(把所有元素设为指定值)
int[] arr4 = new int[5];
Arrays.fill(arr4, 10); // 填充10
System.out.println(Arrays.toString(arr4)); // [10,10,10,10,10]
// 3. copyOfRange:拷贝数组的指定范围
int[] arr5 = Arrays.copyOfRange(arr1, 0, 2); // 拷贝[0,2),即下标0、1
System.out.println(Arrays.toString(arr5)); // [1,2]
}
}
总结(核心要点 + 和 C 语言的区别)
- 数组本质:Java 数组是堆区对象(有 length 属性),C 语言是内存指针;Java 越界抛异常,C 语言不检查;
- 初始化 :Java 支持静态初始化简写(
int[] arr = {1,2,3}),动态初始化有默认值;C 语言无默认值,不能简写; - 引用类型:Java 数组名是引用(存地址),两个引用指向同一数组,修改会互相影响;null 表示引用为空;
- 方法交互:数组传参传地址(和 C 语言指针类似),返回数组更安全(JVM 管理内存);
- 二维数组:Java 是 "数组的数组",支持不规则数组;C 语言是连续二维内存,必须规则;
- 工具类:Java 的 Arrays 类提供 toString、sort、copyOf 等便捷方法,C 语言需手动实现。
结束语
说白了,Java 里的数组是 "带身份证的成品对象"------ 有现成的长度属性,越界会直接报错提醒,初始化能简写,二维数组还能每行长度不一样;
而 C 语言的数组就是 "一块裸内存"------ 得自己算长度,越界了也不吭声,初始化没简写,二维数组必须是规规矩矩的矩阵(每行长度都一样)。