Java · 第 4 章

第 4 章 数组与方法

一维/二维数组与不规则数组;Arrays 工具类(排序、二分查找、拷贝、比较);方法定义与重载;值传递(数组会被改、基本类型不会);可变参数与递归;综合示例:成绩统计。

4.1 数组:一组相同类型的数据

数组是"一排格子",格子编号从 0 开始,长度一旦确定就不能改。

// ① 声明并开辟空间(元素取默认值)
int[] a = new int[5];          // 5 个 int,默认值全是 0
String[] s = new String[3];    // 默认值全是 null
boolean[] f = new boolean[2];  // 默认值全是 false

// ② 声明时直接赋初值(长度由元素个数决定)
int[] b = {3, 1, 4, 1, 5};     // 长度 5
int[] c = new int[]{3, 1, 4};  // 等价写法

// ③ 访问与遍历
System.out.println(b[0]);      // 第一个元素:3
System.out.println(b.length);  // 长度:5(length 是属性,不是方法)

for (int i = 0; i < b.length; i++) {
    System.out.print(b[i] + " ");
}
for (int v : b) {              // for-each:只读遍历,拿不到下标
    System.out.print(v + " ");
}
三条必记
  • 下标范围 0 ~ length-1,越界抛 ArrayIndexOutOfBoundsException(最常见的新手异常)。
  • 数组长度不可变;要"变长"就用集合(第 10 章)或 Arrays.copyOf 造一个新数组。
  • 数组是引用类型:int[] x = y; 是让 x 和 y 指向同一块数据,改 x[0] 会影响 y[0]。
二维数组(数组的数组)
int[][] m = new int[3][4];          // 3 行 4 列,共 12 个 int
System.out.println(m.length);        // 3(行数)
System.out.println(m[0].length);     // 4(第 0 行的列数)

int[][] tri = new int[3][];          // 「不规则数组」:先定行数,每行长度可不同
tri[0] = new int[1];
tri[1] = new int[2];
tri[2] = new int[3];

// 遍历二维数组
for (int i = 0; i < m.length; i++) {
    for (int j = 0; j < m[i].length; j++) {
        System.out.print(m[i][j] + " ");
    }
    System.out.println();
}
课堂练习(3 题)
  1. int[] a = new int[4]; 中 a 的元素默认值是多少?String[] s = new String[2]; 呢? 答:a 元素默认 0;s 元素默认 null(引用类型默认 null)。
  2. 访问 a[a.length] 会发生什么? 答:越界,抛出 ArrayIndexOutOfBoundsException(最后一个合法下标是 length-1)。
  3. 二维数组 int[][] m = new int[2][3]; 一共有几个元素?m.length 是多少? 答:共 2×3=6 个元素;m.length 是 2(行数)。

4.2 Arrays 工具类(排序、查找、拷贝)

写题必用,记住这几个方法能省一半代码。

import java.util.Arrays;

int[] a = {5, 2, 9, 1};

Arrays.toString(a)          // "[5, 2, 9, 1]"   直接打印要转成字符串,否则打印地址
Arrays.sort(a)              // 原地升序排序 → a 变成 [1, 2, 5, 9]
Arrays.binarySearch(a, 5)   // 二分查找,返回下标(找不到返回负数)★ 必须先排序
Arrays.fill(a, 0)           // 全部填 0
Arrays.copyOf(a, 6)         // 复制并扩容到长度 6,多出的位置补 0
Arrays.equals(a, b)         // 比较两个数组内容是否相同(不能用 == 比数组!)
易错
  • 直接 System.out.println(a) 打印的是地址(类似 [I@1b6d3586),要打印内容用 Arrays.toString(a);二维数组用 Arrays.deepToString(m)。
  • 数组比较用 Arrays.equals,== 比的是地址(两个内容相同的数组用 == 也是 false)。
  • binarySearch 必须在已排序的数组上用,否则结果不可信。
课堂练习(2 题)
  1. 打印数组内容为什么要套一层 Arrays.toString()? 答:数组是引用类型,直接打印输出的是哈希地址(如 [I@1b6d3586),转成字符串才能看到元素。
  2. 把数组 {3,1,2} 排序后查找 2 的下标,两步怎么写? 答:Arrays.sort(a); int idx = Arrays.binarySearch(a, 2);(排序后 a=[1,2,3],idx=1)。

4.3 方法:定义、重载与参数传递

"值传递"这一条是所有 Java 考试都要考的。

// 定义:修饰符 返回值类型 方法名(参数列表) { 方法体 }
public static int add(int x, int y) {
    return x + y;            // 有返回值就必须 return 对应类型的值
}

public static void printHi() {   // void 表示不返回值
    System.out.println("hi");
    // return; 可省略
}

// 调用
int sum = add(3, 5);
printHi();
方法重载(Overload)
  • 同一个类里方法名相同、参数列表不同(个数、类型或顺序不同)。
  • 与返回值类型无关:只有返回值不同不算重载,会编译报错。
static int add(int a, int b)       { return a + b; }
static double add(double a, double b) { return a + b; }
static int add(int a, int b, int c)  { return a + b + c; }   // 三个都是重载

add(1, 2);        // 调 int 版
add(1.5, 2.5);    // 调 double 版
参数传递:值传递(重点)
static void change(int n)      { n = 100; }        // 改的是副本
static void change(int[] arr)  { arr[0] = 100; }   // 改的是同一块数组

int x = 10;
change(x);            // x 仍是 10(基本类型:传副本,改不动原值)

int[] a = {1, 2, 3};
change(a);            // a[0] 变成 100(引用类型:传地址副本,能改到对象内容)
  • Java 只有值传递:基本类型传"值的副本",引用类型传"地址的副本"——所以改对象内容会生效,但让引用指向新对象不会生效。
可变参数与递归
// 可变参数:本质是数组,只能放参数列表最后一个
static int sum(int... nums) {
    int s = 0;
    for (int n : nums) s += n;
    return s;
}
sum(1, 2, 3);        // 6
sum();               // 0(可以不传)

// 递归:方法自己调自己,必须有终止条件
static long fact(int n) {
    if (n <= 1) return 1;          // 终止条件,缺了就栈溢出
    return n * fact(n - 1);
}
易错
  • 方法内定义的变量是局部变量,没有默认值,不赋值就用会编译报错。
  • 递归必须有终止条件且每次向终止靠近,否则 StackOverflowError。
  • static 方法里不能直接调用非 static 成员(第 6 章详讲)。
课堂练习(3 题)
  1. 判断:int f(int a) 和 double f(int a) 构成重载吗? 答:不构成,重载只看参数列表;仅返回值不同会报"方法已定义"。
  2. 下面代码执行后 x 的值是多少?int x=1; change(x);(change 里 n=100) 答:x 仍是 1。基本类型参数传的是副本,方法内改动不影响实参。
  3. 递归求阶乘,n=0 时应该返回什么?为什么? 答:返回 1(0! = 1,且作为递归终止条件)。

4.4 综合示例:成绩统计

把数组 + 方法 + 遍历串起来,建议自己在沙盒里敲一遍。

import java.util.Arrays;

public class ScoreStat {
    // 求最大值
    static int max(int[] a) {
        int m = a[0];
        for (int v : a) if (v > m) m = v;
        return m;
    }
    // 求平均值(注意:要先转浮点再除)
    static double avg(int[] a) {
        int s = 0;
        for (int v : a) s += v;
        return s * 1.0 / a.length;
    }
    // 统计及格人数
    static int passCount(int[] a) {
        int c = 0;
        for (int v : a) if (v >= 60) c++;
        return c;
    }

    public static void main(String[] args) {
        int[] scores = {72, 55, 88, 61, 90};
        System.out.println("成绩:" + Arrays.toString(scores));
        System.out.println("最高分:" + max(scores));
        System.out.println("平均分:" + avg(scores));
        System.out.println("及格人数:" + passCount(scores));

        Arrays.sort(scores);
        System.out.println("排序后:" + Arrays.toString(scores));
    }
}
这段代码的三个好习惯
  • 一个方法只做一件事(max/avg/passCount 分开),比全塞进 main 好读好改。
  • 求平均值用 s * 1.0 / 长度,避免整数除法截断(第 2 章的坑)。
  • 方法参数用数组,让同一段逻辑能处理任意长度的数据。

复制到 Java 在线运行环境 直接跑,试试改成绩或加个"最低分"方法。

课堂练习(2 题)
  1. 在上面程序里再加一个方法 min(int[] a),思路是什么? 答:与 max 对称——取 a[0] 为初始最小值,遍历中遇到更小就替换,最后返回。
  2. avg 方法里如果写成 s / a.length 会怎样? 答:整数除法截断,例如总分 366/5 得到 73 而不是 73.2(返回 double 也只是 73.0)。