引入:

在 Java 编程中,数组是最基础且高频使用的数据结构之一。无论是处理批量数据、实现排序算法,还是作为其他复杂结构的底层支撑,数组都扮演着关键角色。本文将从数组的核心概念出发,详细讲解其特性、基本操作,并通过 “求最值”“找缺失数字” 等经典案例,帮助初学者快速掌握数组的使用方法,为后续数据结构学习打下坚实基础。

一、数组是什么?核心概念解析

数组本质是连续存储相同数据类型元素的集合,它就像一排整齐的 “储物柜”—— 每个柜子(元素)类型一致,位置连续,通过 “柜门编号”(下标)就能快速找到对应内容。

1.1 数组的三大核心特性​

数组的设计逻辑围绕 “高效存储与访问” 展开,主要有以下三个关键特性:

特性 详细说明 核心作用
相同数据类型 数组中所有元素必须是同一类型(如int、double),不允许混合存储。 保证内存占用大小统一,可通过 “起始地址 + 元素大小 × 下标” 直接计算元素位置,实现快速访问。
连续的存储空间 数组在创建时必须指定大小,JVM 会为其分配一块连续的内存区域,元素紧密排列。 避免内存碎片,减少元素查找时的内存跳转,提升访问效率。
下标从 0 开始 数组的第一个元素下标为0,第二个为1,最后一个为 “数组长度 - 1”。 符合计算机底层内存地址的计算逻辑(起始地址 + 偏移量),简化元素定位过程。

举个直观例子:若有数组int[] arr = {5,6,4,8},其存储结构与下标对应关系如下:

数组元素 5 6 4 8
下标 0 1 2 3

1.2 常见数据类型的数组内存占用​

不同数据类型的元素,在内存中占用的字节数不同,这直接影响数组的总内存开销。Java 中常用基本数据类型的数组内存特性如下:

数据类型 每个元素占用字节 数组示例 数组总内存(假设长度为 10) 说明
int  4字节(32bit) int[] arr = new int[10] 10×4=40 字节 适用于存储整数(如年龄、数量),是开发中最常用的数组类型之一。
double 8字节(64字节) double[] arr = new double[10] 10×8=80 字节 适用于存储小数(如价格、重量),精度高于float。
char 2字节(16bit) char[] arr = new char[10] 10×2=20 字节 适用于存储单个字符(如字母、符号),支持 Unicode 编码。
boolen 1字节(8bit) boolean[] arr = new boolean[10] 10×1=10 字节 适用于存储布尔值(true/false),常用于标记状态(如 “是否存在”)。

二、Java 数组的创建与基本操作​

掌握数组的创建方式和基础操作,是使用数组的前提。本节将通过具体代码示例,讲解数组的创建、元素访问、遍历等核心操作。​

2.1 数组的两种创建方式​

Java 中创建数组有 “指定长度” 和 “直接赋值” 两种方式,适用于不同场景:​

方式 1:指定长度创建(动态初始化)​

先定义数组长度,后续再逐个赋值(未赋值时,基本数据类型会有默认值,如int默认0,double默认0.0)。

public class ArrayCreate1 {
    public static void main(String[] args) {
        // 1. 创建长度为10的int数组(动态初始化,元素默认值为0)
        int[] arr = new int[10];
        
        // 2. 为数组元素赋值(通过下标赋值)
        arr[0] = 10;  // 给第1个元素(下标0)赋值10
        arr[1] = 20;  // 给第2个元素(下标1)赋值20
        arr[9] = 100; // 给最后1个元素(下标9)赋值100
        
        // 3. 遍历数组,打印所有元素
        System.out.println("动态初始化的数组元素:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " "); 
            // 输出:10 20 0 0 0 0 0 0 0 100
        }
    }
}

方式 2:直接赋值创建(静态初始化)​

创建数组时直接指定所有元素,无需单独定义长度(JVM 会自动根据元素个数计算长度)。

public class ArrayCreate2 {
    public static void main(String[] args) {
        // 1. 直接赋值创建int数组(静态初始化,长度为5)
        int[] arr1 = {1, 2, 3, 4, 5};
        
        // 2. 直接赋值创建double数组(长度为3)
        double[] arr2 = {3.14, 2.71, 1.68};
        
        // 3. 打印数组长度(通过`数组名.length`获取)
        System.out.println("arr1的长度:" + arr1.length); // 输出:5
        System.out.println("arr2的长度:" + arr2.length); // 输出:3
        
        // 4. 遍历arr2,打印所有元素
        System.out.println("arr2的元素:");
        for (int i = 0; i < arr2.length; i++) {
            System.out.print(arr2[i] + " "); 
            // 输出:3.14 2.71 1.68
        }
    }
}

注意事项:​

  • 静态初始化时,{}内的元素个数不能为 0(否则需用动态初始化);​
  • 数组一旦创建,长度不可修改(若需动态扩容,需使用ArrayList等集合类)。​

2.2 数组元素的访问与遍历​

数组元素的访问通过 “下标” 实现,而遍历则是通过循环逐个访问所有元素,是数组操作的核心场景。​

(1)元素访问:通过下标读写​

  • 读取元素:数组名[下标],如int value = arr[0](读取下标 0 的元素);​
  • 修改元素:数组名[下标] = 新值,如arr[2] = 99(将下标 2 的元素改为 99)。
    public class ArrayAccess {
        public static void main(String[] args) {
            int[] arr = {10, 20, 30, 40, 50};
            
            // 1. 读取元素
            int first = arr[0];  // 读取第1个元素,值为10
            int last = arr[4];   // 读取最后1个元素,值为50
            System.out.println("第1个元素:" + first);
            System.out.println("最后1个元素:" + last);
            
            // 2. 修改元素
            arr[2] = 300;  // 将下标2的元素从30改为300
            System.out.println("修改后下标2的元素:" + arr[2]); // 输出:300
            
            // 3. 错误示例:下标越界(数组长度为5,下标最大为4)
            // int error = arr[5]; // 运行时会抛出ArrayIndexOutOfBoundsException异常
        }
    }

    (2)元素遍历:两种常用方式​

    遍历数组有 “普通 for 循环” 和 “增强 for 循环(for-each)” 两种方式,前者可操作下标,后者更简洁(适合仅读取元素的场景)。​

    代码示例:

    public class ArrayTraversal {
        public static void main(String[] args) {
            String[] fruits = {"苹果", "香蕉", "橙子", "葡萄"};
            
            // 方式1:普通for循环(可通过下标操作元素)
            System.out.println("普通for循环遍历:");
            for (int i = 0; i < fruits.length; i++) {
                System.out.println("下标" + i + ":" + fruits[i]);
            }
            
            // 方式2:增强for循环(for-each,仅读取元素,无法获取下标)
            System.out.println("\n增强for循环遍历:");
            for (String fruit : fruits) {
                System.out.println("水果:" + fruit);
            }
        }
    }

    运行结果:

    普通for循环遍历:
    下标0:苹果
    下标1:香蕉
    下标2:橙子
    下标3:葡萄

    增强for循环遍历:
    水果:苹果
    水果:香蕉
    水果:橙子
    水果:葡萄

  • 三、Java 数组经典实战案例​

    理论结合实践才能真正掌握数组用法。本节将通过两个高频案例 ——“求数组最值” 和 “找 0-100 间的缺失数字”,讲解数组在实际开发中的应用。​

    案例 1:查找数组中的最大值与最小值​

    需求:给定一个 int 数组,找出其中的最大值和最小值,并打印结果。​

    思路分析:​

  • 初始化两个变量max和min,默认赋值为数组的第一个元素(下标 0);​
  • 遍历数组,将每个元素与max、min分别对比;​
  • 若元素大于max,更新max;若元素小于min,更新min;​
  • 遍历结束后,max和min即为数组的最值。​
  • 完整代码:

    public class ArrayMaxMin {
        public static void main(String[] args) {
            // 1. 定义待处理的数组(可替换为任意int数组)
            int[] arr = {12, 45, 7, 98, 33, 6, 89, 21};
            
            // 2. 初始化max和min(默认取数组第一个元素)
            int max = arr[0];
            int min = arr[0];
            
            // 3. 遍历数组,对比更新max和min
            System.out.println("数组元素遍历过程:");
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " "); // 打印当前遍历的元素
                
                // 对比更新最大值
                if (arr[i] > max) {
                    max = arr[i];
                }
                // 对比更新最小值
                if (arr[i] < min) {
                    min = arr[i];
                }
            }
            
            // 4. 打印最终结果
            System.out.println("\n");
            System.out.printf("数组中的最大值为:%d\n", max);
            System.out.printf("数组中的最小值为:%d\n", min);
            
            // 额外测试:动态初始化数组的默认值(int默认0)
            int[] brr = new int[6];
            System.out.println("\n动态初始化数组(int)的默认元素:");
            for (int i = 0; i < brr.length; i++) {
                System.out.print(brr[i] + " "); // 输出:0 0 0 0 0 0
            }
        }
    }

    运行结果:

    数组元素遍历过程:

    12 45 7 98 33 6 89 21

    数组中的最大值为:98

    数组中的最小值为:6

    动态初始化数组(int)的默认元素:

    0 0 0 0 0 0

    案例 2:找出 0-100 之间缺失的数字​

    需求:给定一个存储了部分 1-100 整数的数组,找出 1-100 中所有不在数组中的数字,并打印。​

    思路分析:​

  • 遍历 1-100 的所有整数(作为 “目标数字”);​
  • 对每个目标数字,检查其是否在给定数组中(通过内层循环遍历数组);​
  • 用布尔变量isExist标记目标数字是否存在:若存在,isExist=true;若不存在,isExist=false;​
  • 若isExist=false,打印该目标数字(即缺失的数字)。​
  • 完整代码:

    public class ArrayFindMissing {
        public static void main(String[] args) {
            // 1. 定义给定的数组(存储了部分1-100的整数,可自行修改)
            int[] arr = {1, 2, 3, 4, 5, 6, 33, 44, 55, 66, 77, 88, 99};
            
            // 2. 遍历1-100的所有数字,检查是否在数组中
            System.out.println("1-100中缺失的数字如下:");
            int count = 0; // 统计缺失数字的个数
            for (int target = 1; target <= 100; target++) {
                boolean isExist = false; // 标记目标数字是否存在于数组中
                
                // 内层循环:遍历数组,检查目标数字是否存在
                for (int j = 0; j < arr.length; j++) {
                    if (arr[j] == target) {
                        isExist = true; // 找到目标数字,标记为存在
                        break; // 无需继续遍历数组,跳出内层循环
                    }
                }
                
                // 若目标数字不存在,打印并统计
                if (!isExist) {
                    System.out.print(target + " ");
                    count++;
                    // 每10个数字换行,优化打印格式
                    if (count % 10 == 0) {
                        System.out.println();
                    }
                }
            }
            
            // 打印缺失数字的总数
            System.out.println("\n\n1-100中共有 " + count + " 个数字缺失");
        }
    }

    运行结果(部分):

    1-100中缺失的数字如下:

    7 8 9 10 11 12 13 14 15 16

    17 18 19 20 21 22 23 24 25 26

    27 28 29 30 31 32 34 35 36 37

    38 39 40 41 42 43 45 46 47 48

    49 50 51 52 53 54 56 57 58 59

    60 61 62 63 64 65 67 68 69 70

    71 72 73 74 75 76 78 79 80 81

    82 83 84 85 86 87 89 90 91 92

    93 94 95 96 97 98 100

    1-100中共有 87 个数字缺失

    四、Java 数组常见问题与注意事项​

    在使用数组时,初学者容易遇到一些问题,本节整理了高频注意事项,帮助大家避坑:

    常见问题 原因分析 解决方案
    数组下标越界异常 访问的下标小于 0 或大于 “数组长度 - 1”(如长度为 5 的数组,访问下标 5)。 1. 遍历数组时,用i < arr.length控制循环边界(避免硬编码下标);2. 访问元素前,先判断下标是否合法(如if (index >=0 && index < arr.length));3. 使用增强 for 循环遍历(仅读取元素时),避免手动操作下标。
    空指针异常(NullPointerException) 数组变量未初始化(仅定义int[] arr,未用new创建数组)。 1. 确保数组创建时执行new操作(如int[] arr = new int[5]或String[] arr = {"a","b"});2. 避免在数组为null时调用arr.length或访问元素(可先判断if (arr != null));3. 引用类型数组初始化后,需为每个元素单独赋值(如String[] arr = new String[3]; arr[0] = "hello")。
    数组长度不可修改导致的扩容问题 Java 数组是 “固定长度” 结构,创建后长度无法动态调整,若元素数量超过初始长度,会导致数据溢出。 1. 初始化数组时,根据实际需求预估合理长度;2. 若需动态扩容,可使用ArrayList等集合类(底层自动扩容);3. 手动扩容:创建新数组(长度为原数组的 1.5-2 倍),通过System.arraycopy()或Arrays.copyOf()将原数组元素复制到新数组。
    基本数据类型数组与包装类数组混淆 将基本数据类型数组(如int[])误当作包装类数组(如Integer[])使用,导致类型转换异常。 1. 明确区分基本类型数组(int[]/double[])与包装类数组(Integer[]/Double[]);2. 若需将基本类型数组转为包装类数组,可通过循环逐个装箱(如int[] arr = {1,2}; Integer[] boxedArr = new Integer[arr.length]; for(int i=0;i<arr.length;i++) boxedArr[i] = arr[i]),或使用 Java 8+ 的Arrays.stream()(如Integer[] boxedArr = Arrays.stream(arr).boxed().toArray(Integer[]::new))。

    五、Java 数组工具类:Arrays 类的实用方法(高频必备)​

    手动实现数组的排序、复制、查找等功能会增加代码量,Java 提供了java.util.Arrays工具类,封装了大量高效方法,可直接调用,提升开发效率。​

    5.1 Arrays 类核心方法汇总

    方法签名

    功能说明

    示例代码

    static void sort(int[] a)

    对 int 数组进行升序排序(底层为双轴快速排序,效率高)

    int[] arr = {3,1,4,2}; Arrays.sort(arr);(排序后:[1,2,3,4])

    static int binarySearch(int[] a, int key)

    已排序的数组中,二分查找指定元素的下标(未找到返回负数)

    int[] arr = {1,2,3,4}; int index = Arrays.binarySearch(arr, 3);(返回:2)

    static int[] copyOf(int[] original, int newLength)

    复制原数组,返回新数组(新长度可大于或小于原长度,超出部分用默认值填充)

    int[] arr = {1,2,3}; int[] newArr = Arrays.copyOf(arr, 5);(新数组:[1,2,3,0,0])

    static void fill(int[] a, int val)

    将数组所有元素填充为指定值

    int[] arr = new int[3]; Arrays.fill(arr, 6);(数组:[6,6,6])

    static boolean equals(int[] a, int[] a2)

    比较两个数组的元素是否完全相同(长度相同且对应位置元素相等)

    int[] arr1 = {1,2}; int[] arr2 = {1,2}; boolean isEqual = Arrays.equals(arr1, arr2);(返回:true)

    static String toString(int[] a)

    将数组转为字符串(格式:[元素1, 元素2, ...]),方便打印输出

    int[] arr = {1,2,3}; System.out.println(Arrays.toString(arr));(输出:[1, 2, 3])

    5.2 Arrays类实战示例

    import java.util.Arrays;
    
    public class ArraysDemo {
        public static void main(String[] args) {
            // 1. 数组排序
            int[] arr1 = {5, 2, 9, 1, 5, 6};
            Arrays.sort(arr1);
            System.out.println("排序后数组:" + Arrays.toString(arr1)); 
            // 输出:排序后数组:[1, 2, 5, 5, 6, 9]
    
            // 2. 二分查找(需先排序)
            int key = 5;
            int index = Arrays.binarySearch(arr1, key);
            System.out.println("元素 " + key + " 的下标:" + index); 
            // 输出:元素 5 的下标:2(若有重复元素,返回第一个匹配的下标)
    
            // 3. 数组复制(扩容)
            int[] arr2 = Arrays.copyOf(arr1, 8); // 原长度6,扩容到8
            System.out.println("扩容后数组:" + Arrays.toString(arr2)); 
            // 输出:扩容后数组:[1, 2, 5, 5, 6, 9, 0, 0]
    
            // 4. 数组填充
            int[] arr3 = new int[4];
            Arrays.fill(arr3, 3);
            System.out.println("填充后数组:" + Arrays.toString(arr3)); 
            // 输出:填充后数组:[3, 3, 3, 3]
    
            // 5. 数组比较
            int[] arr4 = {1,2,3};
            int[] arr5 = {1,2,3};
            int[] arr6 = {1,2,4};
            System.out.println("arr4与arr5是否相等:" + Arrays.equals(arr4, arr5)); // true
            System.out.println("arr4与arr6是否相等:" + Arrays.equals(arr4, arr6)); // false
        }
    }

    六、数组与 ArrayList 的对比:何时用数组,何时用集合?​

    Java 中数组和ArrayList(集合类)都可存储数据,但适用场景不同,初学者需明确两者区别,避免误用。

    对比维度

    数组(如int[])

    ArrayList(如ArrayList<Integer>)

    数据类型

    1. 支持基本类型(int/double)和引用类型(String/ 对象);2. 类型固定,不可混合存储。

    1. 仅支持引用类型(基本类型需用包装类,如Integer/Double);2. 类型固定,不可混合存储。

    长度特性

    固定长度,创建后不可修改。

    动态长度,元素添加 / 删除时自动扩容(默认初始容量 10,扩容时创建新数组复制元素)。

    核心方法

    无内置方法,需手动实现排序、查找、扩容(或用Arrays工具类)。

    内置丰富方法(add()/remove()/get()/size()等),无需手动处理扩容。

    内存占用

    内存紧凑(无额外开销),适合存储固定数量的简单数据。

    内存开销略大(需存储容量、元素引用等额外信息),但灵活性更高。

    适用场景

    1. 数据量固定且已知(如存储学生成绩、月份天数);2. 性能要求高,需减少内存开销;3. 底层开发或简单数据存储。

    1. 数据量不固定,需动态添加 / 删除元素(如购物车、待办列表);2. 需频繁使用排序、查找等功能,不想手动实现;3. 开发效率优先的业务场景。示例:数组与 ArrayList 实战对比

示例:数组与 ArrayList 实战对比

import java.util.ArrayList;

public class ArrayVsArrayList {
    public static void main(String[] args) {
        // 1. 数组示例(存储固定数量的学生成绩)
        int[] scores = {85, 92, 78, 95}; // 固定长度4
        System.out.println("数组存储的成绩:");
        for (int score : scores) {
            System.out.print(score + " "); // 输出:85 92 78 95
        }
        // 数组无法直接添加元素,需手动扩容
        int[] newScores = new int[5];
        System.arraycopy(scores, 0, newScores, 0, scores.length);
        newScores[4] = 88; // 新增成绩88
        System.out.println("\n数组扩容后:" + Arrays.toString(newScores)); // [85,92,78,95,88]

        // 2. ArrayList示例(存储动态的购物车商品)
        ArrayList<String> cart = new ArrayList<>(); // 动态长度,初始容量10
        cart.add("手机"); // 添加元素
        cart.add("耳机");
        cart.add("充电器");
        System.out.println("\n购物车商品:" + cart); // 输出:[手机, 耳机, 充电器]
        
        cart.remove("耳机"); // 删除元素
        System.out.println("删除耳机后购物车:" + cart); // 输出:[手机, 充电器]
        
        System.out.println("购物车商品数量:" + cart.size()); // 输出:2(动态获取长度)
    }
}

七、总结:Java 数组学习路径与实战建议​

7.1 学习路径​

  1. 基础阶段:掌握数组的定义、创建方式(静态 / 动态初始化)、元素访问与遍历(普通 for 循环 / 增强 for 循环);​
  2. 进阶阶段:理解数组的核心特性(连续存储、固定长度、下标从 0 开始)、内存占用规律,掌握Arrays工具类的使用;​
  3. 实战阶段:通过经典案例(求最值、找缺失数字、数组排序)巩固知识点,理解数组与ArrayList的适用场景差异;​
  4. 深入阶段:学习数组的底层内存模型(如栈与堆的存储区别)、数组扩容的实现原理,为后续学习二维数组、矩阵运算打下基础。​

7.2 实战建议​

  1. 多写代码:手动实现数组的遍历、排序、复制等功能,再对比Arrays工具类的实现,理解 “手动编码” 与 “工具类” 的效率差异;​
  2. 避坑训练:刻意编写触发 “下标越界”“空指针异常” 的代码,观察异常信息,掌握排查方法;​
  3. 场景练习:根据实际需求选择数据结构(如存储固定的员工 ID 用数组,存储动态的用户评论用ArrayList);​
  4. 拓展学习:学习二维数组(如int[][] matrix)、数组与函数的结合(如数组作为方法参数 / 返回值),为后续学习排序算法(如冒泡排序、插入排序)做准备。​

通过以上学习,你不仅能熟练使用数组解决实际问题,还能理解 “固定长度” 数据结构的设计思想,为后续学习更复杂的数据结构(如链表、栈、队列)奠定基础。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐