×

开篇引子

上篇文章我们学了数组的”基本操作”——遍历、求和、找最值。但实际开发中,你遇到的场景远比”存 5 个学生成绩”复杂。比如:

  • 存一个 3×3 的棋盘(五子棋、井字棋)用啥?→ 二维数组
  • 想把数组快速排序、填充、比较怎么搞?→ Arrays 工具类
  • 方法不确定要传多少个参数怎么办?→ 可变参数

这篇就把这三个问题一网打尽。

一、多维数组

1.1 二维数组的定义

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

// 方式一:规则矩形(3行4列)
int[][] matrix = new int[3][4];

// 方式二:直接赋值
int[][] matrix2 = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// 方式三:先声明行数,再分别 new 每一行
int[][] matrix3 = new int[3][];
matrix3[0] = new int[4];
matrix3[1] = new int[4];
matrix3[2] = new int[4];

获取二维数组的行数和列数:

int[][] arr = {{1,2,3},{4,5,6}};
System.out.println("行数:" + arr.length);      // 2
System.out.println("列数:" + arr[0].length);   // 3

1.2 二维数组的遍历

int[][] scores = {
    {85, 90, 78},  // 学生0的三门成绩
    {92, 88, 95},  // 学生1的三门成绩
    {76, 82, 79},  // 学生2的三门成绩
    {88, 85, 91}   // 学生3的三门成绩
};

// 双层 for 循环遍历
for (int i = 0; i < scores.length; i++) {
    System.out.print("学生" + i + ":");
    for (int j = 0; j < scores[i].length; j++) {
        System.out.print(scores[i][j] + " ");
    }
    System.out.println();  // 换行
}

// 增强 for 也可以
for (int[] student : scores) {
    for (int score : student) {
        System.out.print(score + " ");
    }
    System.out.println();
}

运行结果:

学生0:85 90 78
学生1:92 88 95
学生2:76 82 79
学生3:88 85 91

1.3 不规则二维数组(锯齿数组)

既然二维数组是”数组的数组”,那每一行的长度可以不同:

// 杨辉三角:第 n 行有 n+1 个元素
int[][] yangHui = new int[5][];
for (int i = 0; i < yangHui.length; i++) {
    yangHui[i] = new int[i + 1];  // 每行长度递增
    yangHui[i][0] = 1;
    yangHui[i][i] = 1;
    for (int j = 1; j < i; j++) {
        yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j];
    }
}

// 打印
for (int[] row : yangHui) {
    System.out.println(Arrays.toString(row));
}
// 输出:
// [1]
// [1, 1]
// [1, 2, 1]
// [1, 3, 3, 1]
// [1, 4, 6, 4, 1]

这种不规则数组也叫锯齿数组。比如要存储每个学生选修的课程——A 学生选了3门,B 学生选了5门——锯齿数组天然适合。

1.4 三维及以上数组

理论上 Java 支持任意维度的数组,但实际中超过三维就很少见了:

// 三维数组:int[层][行][列]
int[][][] cube = new int[3][4][5];  // 3层、每层4行、每行5列

// 赋值
cube[0][0][0] = 100;
cube[2][3][4] = 200;

可以理解为一个立方体的 x/y/z 坐标。但讲真,工作中用到三维数组的概率极低,知道有这么回事就行,别在这上面钻牛角尖。

二、Arrays 工具类

java.util.Arrays 是 Java 给数组量身定做的工具类,所有方法都是静态的,直接 Arrays.xxx() 就能调用。

2.1 toString()——快速打印

上一篇已经用了,这里再提一下二维数组的打印:

int[][] arr = {{1,2,3},{4,5,6}};
System.out.println(Arrays.toString(arr));
// 输出:[[I@...], [I@...]]  ← 又变成地址了!
// 要用这个:
System.out.println(Arrays.deepToString(arr));
// 输出:[[1, 2, 3], [4, 5, 6]]

记住:一维用 toString(),多维用 deepToString()。不要问我为什么知道,问就是被坑过。

2.2 sort()——排序

int[] arr = {5, 3, 9, 1, 7};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));  // [1, 3, 5, 7, 9]

// 支持指定范围:对 [1, 4) 排序
int[] arr2 = {5, 3, 9, 1, 7};
Arrays.sort(arr2, 1, 4);
System.out.println(Arrays.toString(arr2));  // [5, 1, 3, 9, 7]

Arrays.sort() 底层用的是双轴快排(Dual-Pivot Quicksort),对于基本类型性能很好。记住用法就行,面试问底层实现的时候再说。

2.3 binarySearch()——二分查找

使用前提:数组必须已经排好序。不然结果乱套。

int[] arr = {1, 3, 5, 7, 9};
int index = Arrays.binarySearch(arr, 5);
System.out.println("5 的位置:" + index);  // 2

// 找不到的情况
int index2 = Arrays.binarySearch(arr, 4);
System.out.println(index2);              // -3(插入点:-2-1=-3,表示应该插在位置2)

二分查找的效率是 O(log n),比线性查找的 O(n) 快很多。比如在 100 万个有序数里找一个值,最坏情况下线性查找要 100 万次,二分查找最多 20 次。

2.4 fill()——填充

int[] arr = new int[5];
Arrays.fill(arr, 100);
System.out.println(Arrays.toString(arr));  // [100, 100, 100, 100, 100]

// 指定范围填充
Arrays.fill(arr, 1, 4, 50);
System.out.println(Arrays.toString(arr));  // [100, 50, 50, 50, 100]

2.5 equals() —— 比较数组是否相等

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
int[] c = {1, 2, 4};

System.out.println(a == b);                // false —— 比较的是地址!
System.out.println(Arrays.equals(a, b));   // true  —— 比较的是内容
System.out.println(Arrays.equals(a, c));   // false

再次强调:== 比较引用类型的地址,Arrays.equals() 才比较内容。多维数组用 Arrays.deepEquals()

2.6 copyOf() / copyOfRange() —— 拷贝

上一篇已经介绍过了,这里只是一个汇总:

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

// 完整拷贝
int[] copy = Arrays.copyOf(original, original.length);

// 扩展(多出的补默认值)
int[] expanded = Arrays.copyOf(original, 7);  // [1, 2, 3, 4, 5, 0, 0]

// 截取
int[] part = Arrays.copyOfRange(original, 1, 4);  // [2, 3, 4]

三、可变参数(Varargs)

3.1 为什么需要可变参数?

想象你要写一个求任意多个整数之和的方法。没有可变参数的话:

// 要么写多个重载
int sum2(int a, int b) { ... }
int sum3(int a, int b, int c) { ... }
int sum4(int a, int b, int c, int d) { ... }
// 写到 sum100 吗?😂

// 要么传一个数组
int sum(int[] arr) { ... }
// 调用:sum(new int[]{1, 2, 3, 4, 5});  —— 挺啰嗦的

可变参数就是为了解决这个问题——让调用方可以像传零散参数一样,方法内部自动把它们组装成数组

3.2 基本用法

public class VarargsDemo {
    public static void main(String[] args) {
        System.out.println(sum(1, 2));              // 3
        System.out.println(sum(1, 2, 3, 4, 5));     // 15
        System.out.println(sum());                  // 0 (传 0 个也行)
    }
    
    // 可变参数用 类型... 表示
    public static int sum(int... numbers) {
        int total = 0;
        for (int num : numbers) {
            total += num;
        }
        return total;
    }
}

语法规则:

  • 类型... 参数名 表示可变参数,本质上就是数组
  • 一个方法最多只能有一个可变参数
  • 可变参数必须放在参数列表的最后
// ✅ 正确:可变参数在最后
void print(String prefix, int... numbers) {
    System.out.print(prefix + ": ");
    for (int n : numbers) System.out.print(n + " ");
}

// ❌ 错误:可变参数不在最后
void wrong(int... numbers, String prefix) { }

// ❌ 错误:多个可变参数(不是语法限制,而是二义性)
void multiple(int... a, String... b) { }

3.3 可变参数的调用方式

// 方式一:直接传多个值
sum(1, 2, 3);

// 方式二:传数组(可变参数兼容数组传参)
sum(new int[]{1, 2, 3});

// 方式三:传 0 个参数
sum();

注意一个细节:调用时既能传零散参数也能传数组,但不能传 null——编译器会把它当成一个 null 数组对象,运行时遍历会抛 NullPointerException。

3.4 实际场景:格式化字符串

日常用得最多的可变参数就是 String.format()printf()

String msg = String.format("姓名:%s,年龄:%d,成绩:%.1f", "张三", 20, 88.5);
System.out.println(msg);
// 输出:姓名:张三,年龄:20,成绩:88.5

printf 的第一个参数是格式字符串,后面的参数数量随意——这就是典型可变参数应用。

四、综合练习:学生成绩统计系统

把数组进阶知识串起来:

import java.util.Arrays;

public class ScoreSystem {
    public static void main(String[] args) {
        // 二维数组:3个学生,每人4门课
        int[][] scores = {
            {85, 90, 78, 92},  // 张三
            {70, 65, 88, 72},  // 李四
            {95, 91, 89, 96}   // 王五
        };
        String[] names = {"张三", "李四", "王五"};
        
        System.out.println("===== 成绩统计系统 =====");
        
        // 1. 打印成绩表
        System.out.println("姓名tJavatPythont数据库t算法");
        for (int i = 0; i < names.length; i++) {
            System.out.print(names[i] + "t");
            for (int j = 0; j < scores[i].length; j++) {
                System.out.print(scores[i][j] + "t");
            }
            System.out.println();
        }
        
        // 2. 每个人的平均分(用可变参数方法)
        for (int i = 0; i < names.length; i++) {
            double avg = avg(scores[i][0], scores[i][1], scores[i][2], scores[i][3]);
            System.out.println(names[i] + " 平均分:" + avg);
        }
        
        // 3. 每门课的最高分
        for (int j = 0; j < 4; j++) {
            int[] courseScores = new int[3];
            for (int i = 0; i < 3; i++) {
                courseScores[i] = scores[i][j];
            }
            Arrays.sort(courseScores);
            System.out.println("第" + (j + 1) + "门课最高分:" + courseScores[2]);
        }
        
        // 4. 找出全科优秀(平均分≥85)的学生
        System.out.print("全科优秀学生:");
        for (int i = 0; i < names.length; i++) {
            double avg = avg(scores[i][0], scores[i][1], scores[i][2], scores[i][3]);
            if (avg >= 85) {
                System.out.print(names[i] + " ");
            }
        }
        System.out.println();
    }
    
    // 可变参数方法:求任意多个数的平均值
    public static double avg(int... numbers) {
        int sum = 0;
        for (int n : numbers) {
            sum += n;
        }
        return (double) sum / numbers.length;
    }
}

运行结果:

===== 成绩统计系统 =====
姓名	Java	Python	数据库	算法
张三	85	90	78	92
李四	70	65	88	72
王五	95	91	89	96
张三 平均分:86.25
李四 平均分:73.75
王五 平均分:92.75
第1门课最高分:95
第2门课最高分:91
第3门课最高分:89
第4门课最高分:96
全科优秀学生:张三 王五

总结

  • 多维数组是”数组的数组”,常用的是二维数组,支持不规则锯齿
  • Arrays 工具类提供排序、查找、填充、比较、拷贝等实用方法,开发中直接用
  • 一维用 toString(),多维用 deepToString(),这是最常见的坑
  • 可变参数类型... 参数名 声明,本质是数组语法糖,调用时传零散参数或数组都行

下一篇我们终于要进入方法的深入讲解了——方法定义、重载、递归、值传递,这些是 Java 编程的核心思维。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章

09. 数组与方法综合练习

开篇引子 前面三篇文章讲了数组(一维、二维、...

读出全部

08. 方法详解:定义、重载、递归与值传递

开篇引子——为什么要有方法? 我们的代码一直...

读出全部

06. 数组基础:一维数组、遍历与常见操作

开篇引子——为什么需要数组? 想象一下:你要...

读出全部