跳到内容

第 1 卷 / 第 6 章 / 第 5 课

6.5 实验:数组操作与排障 ​

前置知识:第6章声明、遍历、复制、工具和多维数组

预计时间:35 分钟

练习环境:JDK 17+ 与 Bash;参考实现写入临时目录

完成标志:先写输入契约和边界测试,再完成全部热身题与至少两道实现题

每道题先写合法输入、空输入、null 和预期失败,再动手实现。

热身:先预测再运行 ​

1. 默认值与长度 ​

java
int[] values = new int[3];
System.out.println(java.util.Arrays.toString(values));
System.out.println(values.length);

写下输出,并解释为什么合法索引中没有 3。

2. 增强 for 的重新赋值 ​

java
int[] values = {1, 2, 3};
for (int value : values) {
    value *= 10;
}
System.out.println(java.util.Arrays.toString(values));

预测原数组是否改变,再改成索引循环,让结果成为 [10, 20, 30]。

3. 别名与复制 ​

java
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 或小测试确认。

编译错误:初始化器语法 ​

java
int[] values = new int[3]{1, 2, 3};

修复为 int[] values = {1, 2, 3}; 或 new int[]{1, 2, 3}。

编译错误:数组长度不是方法 ​

java
int[] values = new int[3];
System.out.println(values.length());

修复为 values.length。

运行时错误:行还没有创建 ​

java
int[][] grid = new int[3][];
grid[0][0] = 42;

外层组件 grid[0] 仍是 null。先执行 grid[0] = new int[1]。

逻辑错误:把排序后的索引用于旧数组 ​

java
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。若要保留原位置,需要同时保存值与原索引,或使用对象记录。

参考实现 ​

先独立完成,再对照。先为完整程序准备临时目录:

bash
chapter6_lab_root=$(mktemp -d)
readonly chapter6_lab_root
mkdir -p "$chapter6_lab_root/out"
cd "$chapter6_lab_root"

保存为 ArrayLab.java:

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}})));
    }
}
bash
javac -d out ArrayLab.java
java -cp out ArrayLab

预期输出:

text
[5, 4, 3, 2, 1]
-2
[1, 2, 3, 4, 5, 6]
[[1, 4], [2, 5], [3, 6]]

验收与清理 ​

用空数组、单元素、全负数和不规则行逐项碰边界;再说明逆序为何只交换一半组件、线性合并为何不在循环里扩容、二分搜索为何必须先满足排序前提。排障题还要把编译错误、运行时异常和逻辑错误分开记录。

保存实验记录后清理:

bash
cd
ls -ld -- "$chapter6_lab_root"
rm -r -- "$chapter6_lab_root"

数组把同类值按位置组织起来。下一章将姓名、年龄和成绩组合成一个对象,避免用多条平行数组维持脆弱的索引对应关系。

→ 下一步:面向对象入门

Built with VitePress | Software Systems Atlas