6.5 实验:数组操作与排障
前置知识:第6章声明、遍历、复制、工具和多维数组
预计时间:35 分钟
练习环境:JDK 17+ 与 Bash;参考实现写入临时目录
完成标志:先写输入契约和边界测试,再完成全部热身题与至少两道实现题
每道题先写合法输入、空输入、null 和预期失败,再动手实现。
热身:先预测再运行
1. 默认值与长度
int[] values = new int[3];
System.out.println(java.util.Arrays.toString(values));
System.out.println(values.length);写下输出,并解释为什么合法索引中没有 3。
2. 增强 for 的重新赋值
int[] values = {1, 2, 3};
for (int value : values) {
value *= 10;
}
System.out.println(java.util.Arrays.toString(values));预测原数组是否改变,再改成索引循环,让结果成为 [10, 20, 30]。
3. 别名与复制
int[] original = {4, 5, 6};
int[] alias = original;
int[] copy = original.clone();
alias[0] = 40;
copy[1] = 50;分别写出三个变量看到的数组内容,并画出两个数组对象与三个引用变量。
4. 二分搜索
对 {7, 1, 9, 3} 直接调用 Arrays.binarySearch(values, 7) 是否有可靠结果?写出正确的两步调用,并解码一个找不到的返回值。
实现题
1. 原地逆序
实现 reverse(int[] values),不得创建第二个数组。要求:
null明确拒绝;- 空数组和单元素数组保持不变;
- 奇数长度的中间元素不移动。
2. 最大值
实现 max(int[] values)。本文契约规定 null 抛 NullPointerException,空数组抛 IllegalArgumentException。不要用 0 作为初始最大值,因为全负数组会得到错误答案。
3. 合并两个已排序数组
实现 mergeSorted(int[] left, int[] right),用两个索引在线性时间内生成新数组。调用者承诺输入已升序;进阶版本自行验证这个前置条件。
4. 矩形数组转置
实现 transpose(int[][] matrix)。先拒绝 null 行和不等长行,再生成新数组。验证 3×4、1×0 和空外层数组。1×0 转置后只有空外层数组,无法凭它恢复原始列数;这个表示限制也要写进结果说明。
排障题
以下片段分别包含编译错误、运行时异常或能运行但结果错误的逻辑缺陷。先判断阶段,再用 javac 或小测试确认。
编译错误:初始化器语法
int[] values = new int[3]{1, 2, 3};修复为 int[] values = {1, 2, 3}; 或 new int[]{1, 2, 3}。
编译错误:数组长度不是方法
int[] values = new int[3];
System.out.println(values.length());修复为 values.length。
运行时错误:行还没有创建
int[][] grid = new int[3][];
grid[0][0] = 42;外层组件 grid[0] 仍是 null。先执行 grid[0] = new int[1]。
逻辑错误:把排序后的索引用于旧数组
int[] values = {30, 10, 20};
int[] sorted = values.clone();
java.util.Arrays.sort(sorted);
int index = java.util.Arrays.binarySearch(sorted, 30);
System.out.println(values[index]);index 属于 sorted 的位置空间,不能拿来索引排列不同的 values。若要保留原位置,需要同时保存值与原索引,或使用对象记录。
参考实现
先独立完成,再对照。先为完整程序准备临时目录:
chapter6_lab_root=$(mktemp -d)
readonly chapter6_lab_root
mkdir -p "$chapter6_lab_root/out"
cd "$chapter6_lab_root"保存为 ArrayLab.java:
import java.util.Arrays;
import java.util.Objects;
public class ArrayLab {
static void reverse(int[] values) {
Objects.requireNonNull(values, "values");
for (int left = 0, right = values.length - 1;
left < right;
left++, right--) {
int temporary = values[left];
values[left] = values[right];
values[right] = temporary;
}
}
static int max(int[] values) {
Objects.requireNonNull(values, "values");
if (values.length == 0) {
throw new IllegalArgumentException("empty values");
}
int result = values[0];
for (int index = 1; index < values.length; index++) {
if (values[index] > result) {
result = values[index];
}
}
return result;
}
static int[] mergeSorted(int[] left, int[] right) {
Objects.requireNonNull(left, "left");
Objects.requireNonNull(right, "right");
int[] result = new int[Math.addExact(left.length, right.length)];
int leftIndex = 0;
int rightIndex = 0;
int target = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] <= right[rightIndex]) {
result[target++] = left[leftIndex++];
} else {
result[target++] = right[rightIndex++];
}
}
System.arraycopy(left, leftIndex, result, target,
left.length - leftIndex);
target += left.length - leftIndex;
System.arraycopy(right, rightIndex, result, target,
right.length - rightIndex);
return result;
}
static int[][] transpose(int[][] matrix) {
Objects.requireNonNull(matrix, "matrix");
int columns = matrix.length == 0 ? 0 : rowLength(matrix, 0);
for (int row = 1; row < matrix.length; row++) {
if (rowLength(matrix, row) != columns) {
throw new IllegalArgumentException("not rectangular");
}
}
int[][] result = new int[columns][matrix.length];
for (int row = 0; row < matrix.length; row++) {
for (int column = 0; column < columns; column++) {
result[column][row] = matrix[row][column];
}
}
return result;
}
private static int rowLength(int[][] matrix, int row) {
if (matrix[row] == null) {
throw new IllegalArgumentException("null row: " + row);
}
return matrix[row].length;
}
public static void main(String[] args) {
int[] values = {1, 2, 3, 4, 5};
reverse(values);
System.out.println(Arrays.toString(values));
System.out.println(max(new int[]{-9, -2, -7}));
System.out.println(Arrays.toString(
mergeSorted(new int[]{1, 3, 5}, new int[]{2, 4, 6})));
System.out.println(Arrays.deepToString(
transpose(new int[][]{{1, 2, 3}, {4, 5, 6}})));
}
}javac -d out ArrayLab.java
java -cp out ArrayLab预期输出:
[5, 4, 3, 2, 1]
-2
[1, 2, 3, 4, 5, 6]
[[1, 4], [2, 5], [3, 6]]验收与清理
用空数组、单元素、全负数和不规则行逐项碰边界;再说明逆序为何只交换一半组件、线性合并为何不在循环里扩容、二分搜索为何必须先满足排序前提。排障题还要把编译错误、运行时异常和逻辑错误分开记录。
保存实验记录后清理:
cd
ls -ld -- "$chapter6_lab_root"
rm -r -- "$chapter6_lab_root"数组把同类值按位置组织起来。下一章将姓名、年龄和成绩组合成一个对象,避免用多条平行数组维持脆弱的索引对应关系。