跳到内容

第 1 卷 / 第 5 章 / 第 3 课

5.3 参数、返回值与值传递 ​

前置知识:会声明和调用方法

预计时间:35 分钟

完成标志:能用基本类型和引用类型实验证明 Java 只有值传递

形参接收实参求值结果的副本 ​

java
static void buildItem(String name, int days) {
    System.out.println(name + ": " + days + " days");
}

buildItem("standard item", 3);

name、days 是形参。字符串表达式与整数表达式是实参。Java 从左到右求值实参,把结果值复制给新方法调用中的形参变量。

基本类型值传递 ​

java
static void increase(int value) {
    value++;
    System.out.println("inside=" + value);
}

int count = 10;
increase(count);
System.out.println("outside=" + count);

输出:

text
inside=11
outside=10

形参 value 得到整数 10 的副本,修改它不修改调用者变量 count。

引用类型仍然值传递 ​

java
static void changeFirst(int[] values) {
    values[0] = 99;
}

int[] numbers = {1, 2, 3};
changeFirst(numbers);
System.out.println(numbers[0]); // 99

复制的是引用值。调用者变量和形参副本都能标识同一个数组对象,所以通过任一引用修改数组,另一方都能观察到。

这不等于“引用传递”。重新赋值形参不会改调用者变量:

java
static void replace(int[] values) {
    values = new int[] {7, 8, 9};
    values[0] = 100;
}

int[] numbers = {1, 2, 3};
replace(numbers);
System.out.println(numbers[0]); // 1

不要把引用值直接称为可见内存地址。Java 语言只承诺它能标识对象或为 null,普通代码不能对引用做地址算术。

final 参数不等于不可变对象 ​

java
static void update(final int[] values) {
    values[0] = 42;            // 合法:修改对象
    // values = new int[1];    // 编译错误:不能重赋形参
}

final 约束形参变量只赋值一次,不冻结数组内容。

返回一个结果,而不是试图改调用者局部变量 ​

错误期待:

java
static void swap(int left, int right) {
    int temporary = left;
    left = right;
    right = temporary;
}

调用 swap(a, b) 不会交换调用者的两个局部变量。更清楚的设计是返回结果对象或数组;初学阶段可返回两元素数组:

java
static int[] swapped(int left, int right) {
    return new int[] {right, left};
}

int[] result = swapped(3, 5);

后面学习 record 后,可返回有字段名的结果类型,避免 result[0] 的位置含义。

返回值与失败契约 ​

java
static int divide(int dividend, int divisor) {
    if (divisor == 0) {
        throw new IllegalArgumentException("divisor is zero");
    }
    if (dividend == Integer.MIN_VALUE && divisor == -1) {
        throw new ArithmeticException("division overflows int");
    }
    return dividend / divisor;
}

方法可能正常返回、抛异常,或因更严重错误突然结束。本例拒绝零除数和 MIN_VALUE / -1,其余输入按 Java 整数除法向零截断。文档不要只写“返回 int”,还要说明范围、单位和失败方式。

多个参数与参数对象 ​

java
static int quote(String material, int kilograms,
                 boolean rush, int customerLevel) {
    // ...
}

参数多不自动等于设计错误,但连续出现多个相同类型、布尔开关或经常成组变化时,调用容易写错:quote("iron", 5, true, 2) 难以自解释。可引入有名字的请求对象,或拆分职责。

可变参数是数组语法糖 ​

java
static int sum(int... values) {
    int total = 0;
    for (int value : values) {
        total = Math.addExact(total, value);
    }
    return total;
}

System.out.println(sum(1, 2, 3));

方法体把 values 当数组。调用者也可传 int[]。sum() 收到空数组并返回 0;sum((int[]) null) 收到的是 null,遍历时会抛 NullPointerException。无参数与传入 null 不是一回事。一个方法最多有一个可变参数,且必须位于参数列表末尾。重载解析会优先考虑适用的固定参数形式,再考虑可变参数阶段,混用时需要测试歧义。

Python 与 C++ 边界 ​

Python 也会把对象引用绑定给新的形参名;可变对象能被共同观察,形参重新绑定不改变调用者名字。术语常被描述为 call by sharing,不能据此把 Java 称为引用传递。

C++ 明确区分按值、引用参数和指针。Java 没有能直接别名调用者局部变量的参数语法。

专业速查 ​

现象原因
修改基本形参,调用者不变复制了基本值
修改数组元素,调用者可见两个引用值标识同一可变对象
重新赋值数组形参,调用者不变只改了形参副本
final 数组仍能改元素final 约束引用变量,不冻结对象
想返回多个结果返回有明确结构的结果类型

离开这一页前 ​

用 increase、changeFirst 和 replace 三个例子回答同一个问题:调用时究竟复制了什么值?数组元素可见变化来自共享对象,形参重新赋值不影响调用者变量;final 也只约束形参本身。返回值还要写清范围、单位和失败方式。

每次调用都会进入一层新的执行上下文。

→ 下一步:重载的 API 设计

规范入口:JLS §15.12.4.2:实参求值。

Built with VitePress | Software Systems Atlas