目录

写在前面

一、数组是什么?用生活场景来理解

二、数组的创建与初始化

2.1 创建数组

2.2 两种初始化方式

2.3 几个容易踩的坑

2.4 默认值:不赋值也有值

三、数组的基本使用

3.1 访问元素:通过下标

3.2 遍历数组:三种方式

四、理解数组的内存布局

4.1 JVM内存区域的划分

4.2 基本类型 vs 引用类型

4.3 null:空引用

五、数组作为方法的参数和返回值

5.1 数组作为参数

5.2 数组作为返回值

六、数组的常见操作

6.1 数组拷贝

6.2 查找元素

6.3 冒泡排序

6.4 数组逆序

七、二维数组

7.1 基本用法

7.2 不规则二维数组

八、总结


写在前面

想象这样一个场景:期末考试结束了,老师让你录入班上50名同学的Java成绩。按照我们之前学过的知识,你会怎么做?大概率是定义50个变量:score1score2score3……一直到score50

听起来就很离谱对吧?不仅代码冗长得吓人,而且如果要统计平均分、找最高分,你还得把这50个变量挨个操作一遍。更糟糕的是,如果班级人数增加到100人,难道还要再加50个变量?

显然,我们需要一种更聪明的方式来管理同类型的数据。这就是今天的主角——数组


一、数组是什么?用生活场景来理解

数组可以理解为一排编号连续的储物柜。每个柜子可以存放一件物品,所有柜子的规格是一样的,而且它们紧密排列在一起。

┌──┬──┬──┬──┬──┬──┐
│  │  │  │  │  │  │
│ 0│ 1│ 2│ 3│ 4│ 5│
└──┴──┴──┴──┴──┴──┘

从这个图可以看出数组的几个核心特征:

  1. 空间是连续的——柜子一个挨着一个,没有空隙

  2. 每个位置有编号——编号从0开始,依次递增

  3. 所有位置存放同类型数据——要么全是书,要么全是文具,不能混着装

在Java的世界里,数组就是相同类型元素的集合,在内存中占据一段连续的空间。


二、数组的创建与初始化

2.1 创建数组

Java创建数组的基本语法如下:

// 语法:类型[] 数组名 = new 类型[长度];
int[] scores = new int[10];      // 创建一个能存10个整数的数组
double[] prices = new double[5]; // 创建一个能存5个小数的数组
String[] names = new String[3];  // 创建一个能存3个字符串的数组

这里的 int[]表示"这是一个int类型的数组",new int[10]表示"在堆上开辟10个连续的int空间"。

2.2 两种初始化方式

动态初始化:先指定长度,后续再赋值。

int[] array = new int[10];  // 先创建,长度为10,默认值都是0

静态初始化:创建的同时直接指定具体内容。

int[] array1 = new int[]{1, 2, 3, 4, 5};
double[] array2 = new double[]{1.0, 2.0, 3.0};
String[] array3 = new String[]{"Hello", "Java", "世界"};

静态初始化还有一种更简洁的写法——省略格式

int[] array1 = {1, 2, 3, 4, 5};
double[] array2 = {1.0, 2.0, 3.0};

2.3 几个容易踩的坑

坑一:省略格式不能拆分

int[] array;
array = {1, 2, 3};  // 编译报错!省略格式只能在定义时使用

正确的做法是:

int[] array;
array = new int[]{1, 2, 3};  // OK

坑二:不推荐C语言风格的写法

int arr[] = {1, 2, 3};  // 虽然能编译通过,但不推荐

int[]放在一起更能清晰地表达"这是一个int数组类型"的意思。

2.4 默认值:不赋值也有值

如果你创建了数组但没有显式赋值,Java会给每个元素赋予默认值:

元素类型

默认值

byte/short/int/long

0

float/double

0.0

char

'\u0000'(空字符)

boolean

false

引用类型(如String)

null


三、数组的基本使用

3.1 访问元素:通过下标

数组中的每个元素都有一个唯一的下标(也叫索引),从0开始,到 长度-1结束。

int[] array = {10, 20, 30, 40, 50};

System.out.println(array[0]);  // 10
System.out.println(array[1]);  // 20
System.out.println(array[4]);  // 50

// 也可以修改
array[0] = 100;
System.out.println(array[0]);  // 100

特别提醒:下标不能越界。数组长度为5,有效下标是0到4。如果访问 array[5],程序会抛出 ArrayIndexOutOfBoundsException(数组下标越界异常)。

3.2 遍历数组:三种方式

方式一:普通for循环

int[] array = {10, 20, 30, 40, 50};
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}

这里用到了 array.length来获取数组的长度,避免了硬编码。

方式二:for-each循环

int[] array = {10, 20, 30, 40, 50};
for (int x : array) {
    System.out.println(x);
}

for-each的优点是语法简洁,不用操心下标和边界条件。但它有一个局限:拿不到当前元素的下标。如果你需要知道元素的位置,还是要用普通for循环。

方式三:借助Arrays工具类

import java.util.Arrays;

int[] array = {10, 20, 30, 40, 50};
System.out.println(Arrays.toString(array));
// 输出:[10, 20, 30, 40, 50]

Arrays.toString()会将数组转换成美观的字符串格式,非常适合调试时快速查看数组内容。


四、理解数组的内存布局

4.1 JVM内存区域的划分

要真正理解数组,必须了解它在内存中是怎么存的。JVM的内存主要分为以下几个区域:

  • 程序计数器:记录当前执行到哪条指令

  • 虚拟机栈:存储方法调用时的局部变量等信息

  • :存放所有new出来的对象(包括数组)

  • 方法区:存放类信息、常量、静态变量等

对我们来说,现阶段重点关心

4.2 基本类型 vs 引用类型

int a = 10;          // 基本类型变量
int[] arr = new int[]{1, 2, 3};  // 引用类型变量

这两者在内存中的表现完全不同:

  • a是基本类型变量,它的空间中直接存储值 10

  • arr是引用类型变量,它的空间中存储的是数组对象在堆上的地址

栈空间:
┌─────────┐
│ a = 10  │
├─────────┤
│ arr ───────┐
└─────────┘  │
             ▼
堆空间:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
└───┴───┴───┘

这就是为什么我们说"数组是引用类型"——变量 arr并不直接"包含"数组元素,而是"指向"数组对象。

4.3 null:空引用

int[] arr = null;
System.out.println(arr[0]);  // NullPointerException!

null表示"这个引用没有指向任何对象"。试图通过null引用去访问数组元素,会抛出 NullPointerException(空指针异常)。这是Java程序中最常见的异常之一,务必小心。


五、数组作为方法的参数和返回值

5.1 数组作为参数

传基本类型 vs 传数组

public static void main(String[] args) {
    int num = 0;
    changeNum(num);
    System.out.println("num = " + num);  // 0,没变
    
    int[] arr = {1, 2, 3};
    changeArray(arr);
    System.out.println("arr[0] = " + arr[0]);  // 100,变了!
}

public static void changeNum(int x) {
    x = 10;
}

public static void changeArray(int[] a) {
    a[0] = 100;
}

为什么一个变了,一个没变?

  • 传基本类型时,传递的是值的副本,方法内部修改不影响外部

  • 传数组(引用类型)时,传递的是地址的副本,但两个引用指向同一个数组对象,通过任何一个引用修改数组内容,另一个引用也能看到

5.2 数组作为返回值

方法不仅可以接收数组参数,也可以返回数组:

public static int[] createFibonacci(int n) {
    int[] fib = new int[n];
    if (n >= 1) fib[0] = 1;
    if (n >= 2) fib[1] = 1;
    for (int i = 2; i < n; i++) {
        fib[i] = fib[i-1] + fib[i-2];
    }
    return fib;
}

// 调用
int[] result = createFibonacci(10);
System.out.println(Arrays.toString(result));
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

六、数组的常见操作

6.1 数组拷贝

int[] original = {1, 2, 3, 4, 5};

// 方式一:使用Arrays.copyOf
int[] copy1 = Arrays.copyOf(original, original.length);

// 方式二:使用System.arraycopy
int[] copy2 = new int[original.length];
System.arraycopy(original, 0, copy2, 0, original.length);

// 方式三:自己实现
public static int[] myCopyOf(int[] arr) {
    int[] ret = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        ret[i] = arr[i];
    }
    return ret;
}

6.2 查找元素

顺序查找:适用于无序数组

public static int find(int[] arr, int target) {
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == target) {
            return i;
        }
    }
    return -1;  // 没找到
}

二分查找:适用于有序数组,效率极高

public static int binarySearch(int[] arr, int target) {
    int left = 0;
    int right = arr.length - 1;
    
    while (left <= right) {
        int mid = (left + right) / 2;
        if (target < arr[mid]) {
            right = mid - 1;
        } else if (target > arr[mid]) {
            left = mid + 1;
        } else {
            return mid;
        }
    }
    return -1;
}

二分查找的核心思想是每次排除一半的数据。对于一个长度为10000的有序数组,最多只需要14次比较就能找到目标。

6.3 冒泡排序

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;
            }
        }
    }
}

当然,实际开发中直接用 Arrays.sort(arr)就好,Java内置的排序算法比我们自己写的冒泡高效得多。

6.4 数组逆序

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 基本用法

二维数组本质上是一个特殊的一维数组——它的每个元素又是一个一维数组。

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.print(arr[row][col] + "\t");
    }
    System.out.println();
}
  • arr.length表示行数

  • arr[row].length表示该行的列数

7.2 不规则二维数组

Java支持不规则二维数组,即每一行的列数可以不同:

int[][] arr = new int[2][];
arr[0] = new int[3];  // 第一行3列
arr[1] = new int[5];  // 第二行5列

这在某些场景下非常灵活,比如存储杨辉三角。


八、总结

今天我们系统地学习了Java数组的方方面面:

  1. 数组的概念:相同类型元素的连续集合

  2. 创建和初始化:动态初始化 vs 静态初始化

  3. 基本操作:下标访问、遍历、长度获取

  4. 内存原理:引用类型、栈与堆、null

  5. 方法交互:数组作为参数和返回值

  6. 常见算法:拷贝、查找、排序、逆序

  7. 二维数组:规则和不规则两种形态

数组是Java编程的基石,几乎所有的数据结构(列表、队列、栈等)底层都离不开数组。掌握好数组,你就为后续学习更高级的数据结构打下了坚实的基础。

下一篇文章,我们将进入Java面向对象编程的世界——类和对象,敬请期待!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐