Java数组详解

【本节目标】

  1. 搞懂 Java 数组的本质,告别 C 语言数组的思维定式;
  2. 熟练掌握数组的创建、初始化、遍历等核心用法;
  3. 吃透数组作为引用类型的特性,搞定数组和方法的互操作;
  4. 会写数组常见练习题(拷贝、排序、逆序等),掌握 Java 内置数组工具类。

前言:Java 数组 vs C 语言数组(核心差异)

C 语言的数组是 "裸内存块",靠指针操作;Java 的数组是 "对象",有自己的属性(比如length),还受 JVM 内存管理保护 ------ 简单说:C 语言数组是 "毛坯房",Java 数组是 "精装修房",更安全、更易用,但用法差异要记牢

1. 数组的基本概念

1.1 为什么要使用数组?

生活例子:你有 100 个同学的成绩要存,如果用score1score2...score100这 100 个变量,写起来要疯;用数组score[100],一个变量就能存 100 个值,遍历、修改都方便。

核心作用:批量存储相同类型的数据,统一管理和操作,避免变量泛滥。

1.2 什么是数组?

Java 数组的官方定义:相同数据类型的元素组成的固定长度的有序集合。✅ 和 C 语言的相同点:

  1. 元素类型必须一致;
  2. 长度一旦确定,不能修改(都是 "定长");
  3. 下标从 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 语言的区别:

  1. C 语言:int arr[5];(不用 new,直接在栈 / 堆创建);
  2. 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 语言的区别:

  1. C 语言:动态初始化(比如int *arr = malloc(5*sizeof(int));)需要手动分配内存,且不初始化时元素是随机值;
  2. 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. 编译器会根据{}里的元素个数自动确定长度(比如{1,2,3}长度就是 3);

  2. {}里的元素类型必须和数组类型一致(比如int[] arr = {1, 2.0}编译报错);

  3. 简写只能在声明时用,不能分开写:

    复制代码
    // 错误
    int[] arr;
    arr = {1,2,3}; // 编译报错!简写只能int[] arr = {1,2,3};
    // 正确
    arr = new int[]{1,2,3};

和 C 语言的区别:

  1. C 语言静态初始化:int arr[] = {1,2,3};(可以),但int arr[4] = {1,2,3};(剩余元素补 0);
  2. 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

⚠️ 注意事项(重点避坑):

  1. 随机访问:数组是连续内存,通过下标能直接访问任意元素(和 C 语言一样);

  2. 下标越界 :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 语言的区别:

  1. C 语言:for(int i=0; i<5; i++)(要手动写长度,或用 sizeof 计算);
  2. 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};

  1. 栈区:存arr(引用变量),里面装的是 "数组对象在堆区的地址";
  2. 堆区:存真正的数组元素[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 语言的区别:

  1. C 语言:数组传参本质是传指针,void changeArray(int arr[])等价于void changeArray(int *arr)
  2. 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 语言的区别:

  1. C 语言:for(int i=0; i<2; i++) for(int j=0; j<3; j++)(必须手动写行列数);
  2. 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 语言的区别)

  1. 数组本质:Java 数组是堆区对象(有 length 属性),C 语言是内存指针;Java 越界抛异常,C 语言不检查;
  2. 初始化 :Java 支持静态初始化简写(int[] arr = {1,2,3}),动态初始化有默认值;C 语言无默认值,不能简写;
  3. 引用类型:Java 数组名是引用(存地址),两个引用指向同一数组,修改会互相影响;null 表示引用为空;
  4. 方法交互:数组传参传地址(和 C 语言指针类似),返回数组更安全(JVM 管理内存);
  5. 二维数组:Java 是 "数组的数组",支持不规则数组;C 语言是连续二维内存,必须规则;
  6. 工具类:Java 的 Arrays 类提供 toString、sort、copyOf 等便捷方法,C 语言需手动实现。

结束语

说白了,Java 里的数组是 "带身份证的成品对象"------ 有现成的长度属性,越界会直接报错提醒,初始化能简写,二维数组还能每行长度不一样;

而 C 语言的数组就是 "一块裸内存"------ 得自己算长度,越界了也不吭声,初始化没简写,二维数组必须是规规矩矩的矩阵(每行长度都一样)。

相关推荐
张np8 小时前
java基础-ConcurrentHashMap
java·开发语言
一嘴一个橘子9 小时前
spring-aop 的 基础使用 - 4 - 环绕通知 @Around
java
小毅&Nora9 小时前
【Java线程安全实战】⑨ CompletableFuture的高级用法:从基础到高阶,结合虚拟线程
java·线程安全·虚拟线程
冰冰菜的扣jio9 小时前
Redis缓存中三大问题——穿透、击穿、雪崩
java·redis·缓存
小璐猪头9 小时前
专为 Spring Boot 设计的 Elasticsearch 日志收集 Starter
java
ps酷教程9 小时前
HttpPostRequestDecoder源码浅析
java·http·netty
闲人编程9 小时前
消息通知系统实现:构建高可用、可扩展的企业级通知服务
java·服务器·网络·python·消息队列·异步处理·分发器
栈与堆10 小时前
LeetCode-1-两数之和
java·数据结构·后端·python·算法·leetcode·rust
OC溥哥99910 小时前
Paper MinecraftV3.0重大更新(下界更新)我的世界C++2D版本隆重推出,拷贝即玩!
java·c++·算法