- 0
- 590 words
开篇引子
上篇文章我们学了数组的”基本操作”——遍历、求和、找最值。但实际开发中,你遇到的场景远比”存 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 编程的核心思维。