CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Java 语言基础/课时/数组
RawBlame
课时目录14 节

Java 语言基础

  1. 01Java 与 JVM 是什么
  2. 02基本语法与 main
  3. 03基本类型与引用类型
  4. 04条件与循环
  5. 05方法与参数
  6. 06数组
  7. 07类与封装
  8. 08继承接口与多态
  9. 09集合框架入门
  10. 10字符串
  11. 11异常处理
  12. 12文件 IO 入门
  13. 13Maven 与项目结构
  14. 14综合练习与 dsa-java 分工

数组

一维、二维、Arrays 工具类。

30 分钟

数组

这一课你将能做到

  1. 用三种方式创建数组并正确初始化,理解默认值规则,避免越界异常
  2. 熟练使用 Arrays 工具类的打印、排序、复制、填充、比较方法
  3. 说清数组与 ArrayList 的取舍依据,理解「数组是引用类型」带来的复制行为

为什么要学

数组是最原始的批量存储结构,也是几乎所有高级容器(ArrayList、HashMap)的底层积木。后端开发里你更多用 List,但底层碰到的全是数组:

  • MySQL 驱动把一行行结果装进数组再组装成 List
  • main(String[] args) 的参数就是数组
  • Spring 的 @Value("${servers}") 注入配置列表、String... 可变参数,本质都是数组
  • 算法题第一关(两数之和、二分查找)全是数组

不理解数组,集合阶段的扩容、拷贝、性能全都似懂非懂。

先建立图像

数组 = 一排编了号的储物柜:

索引:    0      1      2      3      4
      ┌──────┬──────┬──────┬──────┬──────┐
值:   │  101 │  102 │  103 │  104 │  105 │
      └──────┴──────┴──────┴──────┴──────┘
         ▲
   arr[0] 从 0 开始编号!最后一个下标 = length - 1

三个铁律:定长(创建后不能加格子)、同型(所有格子装同一类型)、从 0 编号。

跟着做

步骤 1:三种创建方式

新建 ArrayDemo.java:

import java.util.Arrays;

public class ArrayDemo {
    public static void main(String[] args) {
        // 写法 1:先定长度,后赋值(长度必须给出)
        int[] ids = new int[3];
        ids[0] = 101;
        ids[1] = 102;
        ids[2] = 103;
        System.out.println(ids[0]);        // 101

        // 写法 2:字面量直接初始化(最常用)
        String[] names = {"mysql", "redis", "spring"};
        System.out.println(names.length);  // 3

        // 写法 3:new + 字面量(适合「先声明后赋值」场景)
        double[] prices = new double[]{9.9, 19.9};
        System.out.println(Arrays.toString(prices));  // [9.9, 19.9]
    }
}

步骤 2:默认值与默认初始化

public class DefaultValues {
    public static void main(String[] args) {
        int[] a = new int[3];
        double[] b = new double[2];
        boolean[] c = new boolean[2];
        String[] d = new String[2];    // 引用类型默认 null!

        System.out.println(a[0]);      // 0
        System.out.println(b[0]);      // 0.0
        System.out.println(c[0]);      // false
        System.out.println(d[0]);      // null
        System.out.println(d[0].length());  // 炸:NullPointerException
    }
}

数组的每个格子都有默认值:整型 0、浮点 0.0、boolean false、引用类型 null。「null 格子直接调方法」是新手 NPE 高发区。

步骤 3:遍历的两种姿势

public class ArrayLoop {
    public static void main(String[] args) {
        int[] scores = {88, 92, 75, 60};

        // 普通 for:需要下标时用
        for (int i = 0; i < scores.length; i++) {
            System.out.printf("第 %d 个: %d%n", i + 1, scores[i]);
        }

        // 增强 for:只关心值时用
        int sum = 0;
        for (int s : scores) {
            sum += s;
        }
        System.out.println("总分: " + sum + ", 均分: " + (double) sum / scores.length);

        // 求最值 + 倒序遍历
        int max = scores[0];
        for (int i = scores.length - 1; i >= 0; i--) {
            if (scores[i] > max) max = scores[i];
        }
        System.out.println("最高分: " + max);
    }
}

步骤 4:Arrays 工具类全家桶

import java.util.Arrays;

public class ArraysUtilDemo {
    public static void main(String[] args) {
        int[] a = {5, 2, 8, 1};

        // 打印:直接 println 会输出 [I@1b6d3586(地址),必须用 toString
        System.out.println(Arrays.toString(a));       // [5, 2, 8, 1]

        // 排序:原地升序(双轴快排)
        Arrays.sort(a);
        System.out.println(Arrays.toString(a));       // [1, 2, 5, 8]

        // 二分查找:必须先排序!返回下标,找不到返回负数
        int idx = Arrays.binarySearch(a, 5);
        System.out.println(idx);                      // 2

        // 复制:扩容式复制,超出部分补默认值
        int[] bigger = Arrays.copyOf(a, 6);
        System.out.println(Arrays.toString(bigger));  // [1, 2, 5, 8, 0, 0]

        // 区间复制:含头不含尾
        int[] part = Arrays.copyOfRange(a, 1, 3);
        System.out.println(Arrays.toString(part));    // [2, 5]

        // 填充:把所有格子填成同一个值(初始化默认值常用)
        int[] filled = new int[4];
        Arrays.fill(filled, -1);
        System.out.println(Arrays.toString(filled));  // [-1, -1, -1, -1]

        // 比较:逐元素比较,数组不能用 == 或 equals 比(见步骤 6)
        System.out.println(Arrays.equals(new int[]{1, 2}, new int[]{1, 2})); // true
    }
}

步骤 5:数组是引用类型——复制行为实证

import java.util.Arrays;

public class ArrayRefDemo {
    public static void main(String[] args) {
        int[] original = {1, 2, 3};

        int[] alias = original;               // 赋值:只是多个「遥控器」,同一个数组
        alias[0] = 99;
        System.out.println(Arrays.toString(original)); // [99, 2, 3] 被改了!

        int[] copy = Arrays.copyOf(original, original.length); // 真拷贝
        copy[0] = -1;
        System.out.println(Arrays.toString(original)); // [99, 2, 3] 不受影响
        System.out.println(original == alias);         // true:同一对象
        System.out.println(original == copy);          // false:不同对象
    }
}

步骤 6:二维数组

import java.util.Arrays;

public class TwoDArray {
    public static void main(String[] args) {
        // 3 行 2 列:想象成「数组的数组」
        int[][] matrix = {
            {1, 2},
            {3, 4},
            {5, 6}
        };
        System.out.println(matrix.length);        // 3(行数)
        System.out.println(matrix[0].length);     // 2(第一行列数)
        System.out.println(matrix[1][0]);         // 3

        // 遍历:外层行,内层列
        for (int[] row : matrix) {
            System.out.println(Arrays.toString(row));  // [1, 2] ...
        }

        // 经典坑:打印二维数组不能用 Arrays.toString,要用 deepToString
        System.out.println(Arrays.toString(matrix));      // 地址垃圾
        System.out.println(Arrays.deepToString(matrix));  // [[1, 2], [3, 4], [5, 6]]
    }
}

讲清楚要点

  1. 数组是定长的对象:length 是属性不是方法(arr.length,注意 String 是 length() 方法——易混)。
  2. 合法下标 0 ~ length-1:越界抛 ArrayIndexOutOfBoundsException,运行时才炸,编译器不拦。
  3. 赋值 ≠ 复制:int[] b = a 只复制地址;真复制用 Arrays.copyOf 或 a.clone()。
  4. 数组比不了内容:== 比地址;数组的 equals 没重写、等价于 ==;比内容必须 Arrays.equals(多维用 deepEquals)。
  5. 数组转 List 有陷阱:Arrays.asList(a) 返回的是定长视图,不能增删;对象数组才支持,int[] 转出来的是「装了一个数组的 List」。
  6. 性能特征:按下标随机访问 O(1);中间插入/删除要整体挪动 O(n) 且定长做不到——这正是集合(第 9 课)存在的理由。

你可能会懵的地方

  • int[] a 和 int a[] 哪个对? 都合法,后者是 C 风格遗产。行业规范一律 int[] a——类型信息集中在左边,可读性好。

  • 数组为什么从 0 开始编号? 索引本质是「偏移量」:a[i] 的地址 = 首地址 + i × 元素大小。从 0 开始让地址计算最简单,这是 1960 年代 BCPL 以来的传统。

  • Arrays.asList 之后 add 报错? Arrays.asList 返回 java.util.Arrays 内部的定长 List 视图,底层还是那个数组,add/remove 直接抛 UnsupportedOperationException。要可变的:new ArrayList<>(Arrays.asList(...))。

  • 什么时候用数组、什么时候用 List? 长度固定、性能敏感(算法题、底层库)用数组;日常业务几乎一律 List——可变长、API 丰富、能配合泛型。本站后端业务代码 95% 用 List。

  • int[] 能直接赋给 Object[] 吗? 不能。基本类型数组和对象数组没有继承关系,Object[] o = new int[3] 编译错。数组协变只发生在「同一元素类型的引用类型」之间(如 String[] → Object[],且这种协变本身有坑)。

错法对照

错法问题正解
int[] a = new int[3]{1,2,3};语法冲突,两者选一new int[]{1,2,3} 或 new int[3]
System.out.println(arr)打印出 [I@1b6d3586Arrays.toString(arr)
for (int i = 0; i <= arr.length; i++)越界异常i < arr.length
int[] b = a; b[0]=9; 以为改的是 ba 也被改Arrays.copyOf(a, a.length)
if (arr1 == arr2) 或 arr1.equals(arr2)比地址,内容相同也 falseArrays.equals(arr1, arr2)
String[] s = new String[2]; s[0].length()格子是 null,NPE先赋值再使用

卡点急救

  1. ArrayIndexOutOfBoundsException:打印报错行号,检查条件是否 <= length、下标是否从 1 开始数。空数组 new int[0] 的任何下标都非法。
  2. 打印数组显示 [I@xxxx / [Ljava.lang.String;@xxxx:[ 开头加类型字母是数组的类名,说明你打印了对象地址。换 Arrays.toString。
  3. ** NullPointerException(数组版)**:引用类型数组格子默认 null。遍历时先判空,或创建时就用字面量初始化。
  4. 排序后二分查找结果诡异:binarySearch 前必须 Arrays.sort,否则结果无意义(返回负数不一定是「不存在」的位置)。
  5. 增强 for 里改数组没生效(存的是值类型):for (int s : scores) s = 0; 改的是局部变量。要改原数组必须用下标:scores[i] = 0。

面试常问

  • 数组与 ArrayList 的区别? 定长 vs 动态扩容(1.5 倍增长);基本类型友好 vs 只能装对象(泛型擦除);语法朴素 vs API 丰富;连续内存 vs 底层也是数组。数组适合固定规模/性能敏感场景。

  • 数组按下标访问为什么是 O(1)? 地址 = 首地址 + 下标 × 元素大小,一次乘加直达,无需遍历。这也是数组有序时二分查找能到 O(log n) 的前提。

  • ==、equals、Arrays.equals 三者对数组的区别? == 比地址;equals 未重写等同 ==;Arrays.equals 逐元素比较。二维/嵌套结构用 deepEquals。

  • Arrays.copyOf 和 System.arraycopy 的区别? copyOf 底层调用 System.arraycopy,返回新数组、可扩容;System.arraycopy 是 native 方法,写入已有目标数组、性能最优(ArrayList 扩容就用它)。

  • 冒泡排序手写一遍(算法面试热身) 双层循环:外层控制轮数,内层相邻比较交换,每轮把最大值「冒」到末尾。写在草稿上,注意内层边界 length - 1 - i。

自测三问

  1. String[] arr = new String[3] 后直接 arr[0].toUpperCase() 会发生什么?为什么?
  2. int[] a = {1,2}; int[] b = a; b[0] = 9; 后 a[0] 是多少?想要互不影响怎么做?
  3. 打印一个 int[][] 应该用哪个方法?为什么 Arrays.toString 不行?

小结一句话

数组是一排从 0 编号的定长同型格子:length 无括号、下标到 length-1、赋值只传地址、比内容找 Arrays、打印找 toString——数组是理解一切集合的底座。

延伸

  • Oracle:数组
  • 下一课:类与封装——把字段和方法装进对象,写出本站后端同款的 User 实体类。
加载本节练习…
← 上一课方法与参数下一课 →类与封装