# 方法：把一段代码打包成可以反复调用的“工序”

如何定义和调用方法、形参与实参、按值传递、return 的双重作用，以及一次调用从实参求值到返回调用点的完整执行顺序

> 方法 · 参数与返回值 · 按值传递 · 约 6 分钟 · 10 月 17 日

## 本篇要点

1. 方法把一段完成特定任务的代码打包成有名字、可以反复调用的单元，解决的正是循环解决不了的问题：同一段逻辑在程序的不同位置被复用。
2. 方法声明里必需的四样东西是返回类型、方法名、一对圆括号、花括号里的方法体；返回类型写明这个方法会交回哪种值，不需要结果就写 void。
3. 定义里的参数叫形参，调用时传进去的值叫实参；实参的个数、类型、顺序都必须和形参对上，否则编译不过。
4. Java 传参是按值传递：调用时把实参的值复制一份给形参，形参是方法内新建的变量，在方法里改它不会影响外面的变量，这正是方法必须靠 return 把结果送出去的原因。
5. return 同时做两件事：交出值、立刻结束整个方法，所以写在 return 后面的代码永远不会执行；非 void 方法必须在每一条可能的路径上都走到 return，否则编译器报错。
6. 调用的执行顺序是：先算实参，再把控制权交给方法体并为这次调用新建一套参数和局部变量，遇到 return 时交出值、销毁这套变量、回到调用点的位置继续往下执行。
7. 每次调用都对应一块独立的栈帧，方法返回后就被丢弃，所以方法里叫 a、外面叫 x 的两个变量互不相干，同一方法被调用多次也不会互相污染。
8. 方法在哪里被调用，执行完就回到哪里；方法本身不关心调用它的是 main、循环还是另一个方法。

---

上一篇我们用循环解决了“在同一个地方重复执行”。但循环只能在它所在的位置一遍遍转圈。真正的麻烦是：同一段逻辑要在程序的好几个地方用，甚至要在循环里一次次被调用。把代码复制三遍能跑，可一旦要改，就得同时改三处，改漏一处就出 bug。

**方法**（method）就是冲着这件事来的：给一段完成特定任务的代码起个名字，打包成一个喊一声就能执行的单元。本篇只讲清四件事——怎么定义方法、参数怎么把外面的值递进去、返回值怎么把结果带回来、以及调用发生时程序按什么顺序走。

## 方法长什么样

```java
public static int max(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}
```

逐块拆开看：

- `public static` 是修饰符，现在照抄就行；它们的含义等学完“类与对象”再回来说。
- `int` 是**返回类型**，说明这个方法会交回一个整数。
- `max` 是方法名。
- `(int a, int b)` 是**参数列表**。每个参数都得写清类型和名字，用逗号隔开；没有参数就写一对空括号。
- `{ … }` 是**方法体**，里面可以写任何你学过的语句——`if`、`for` 都可以（上面就用了上一篇学的 `if`）。

Oracle 官方教程的意思很直接：方法声明里只有四样东西是必需的——返回类型、方法名、一对圆括号、花括号包住的方法体 [1]。注意方法名后面那对括号不是装饰，它是“这是个方法”的标志。

## 参数：外面把值递进去

定义里的 `int a`、`int b` 叫**形参**（parameter），它们是两个只存在于方法内部的变量。调用时写 `max(x, y)`，括号里的 `x`、`y` 叫**实参**（argument），是真实的、当场算出来的值。Oracle 把两者的区别说得很清楚：参数是声明里的那串变量，实参是调用时真正传进去的值 [3]。

两条硬规则：数量和类型必须对上，**顺序也必须对上**——`max(3, 7)` 和 `max(7, 3)` 都能编译，结果却完全不同。

更关键的一条是：Java 传参是**按值传递**（pass by value），也就是把实参的值**复制一份**交给形参。

```java
public static void addOne(int n) {
    n = n + 1;
}

public static void main(String[] args) {
    int a = 5;
    addOne(a);
    System.out.println(a);   // 打印 5，不是 6
}
```

调用 `addOne(a)` 时，Java 新建了一个变量 `n`，把 `a` 的值 5 复制进去。方法里把 `n` 改成 6，改的是这个副本，外面的 `a` 一点没动。Oracle 的说法是：基本类型参数按值传入，对参数的任何修改只存在于方法内部，方法返回后这些修改就丢失了 [3]。

这条机制顺带解释了：**方法为什么需要返回值**。既然它改不动外面的变量，想把算出来的结果送出去，就只能靠 `return`。

## 返回值：方法把结果带回来

有结果要送出，就先在声明里写清它是哪种类型，再在方法体里用 `return` 把值交出去。`max` 的返回类型是 `int`，所以 `max(3, 7)` 这个调用写完就是**一个 int 值**，可以直接赋给变量：

```java
int bigger = max(3, 7);   // bigger 得到 7
```

`return` 有两件事同时发生：把值交出去；**立刻结束整个方法**。`max` 里一旦执行 `if (a > b) return a;`，后面那句 `return b;` 就根本不会跑到了，写在 `return` 之后的语句也永远轮不到执行。

这也带来一条编译规则：非 `void` 的方法必须保证**每一条可能的路径**上都走到 `return`，否则编译器直接报错 [2]。写得再正确也不行——只要编译器认为存在一条走到底却没有 `return` 的路径，就不放行。

类型也必须对得上：声明返回 `int` 就不能返回 `boolean`；反过来，声明为 `void` 的方法不允许 `return` 一个值，`void` 里写 `return;` 只表示“提前收工” [2]。

不需要结果的方法就写 `void`。比如把上一篇的 `for` 循环整套搬进方法体：

```java
public static void printStars(int n) {
    for (int i = 0; i < n; i++) {
        System.out.print("*");
    }
    System.out.println();
}
```

## 调用时，程序到底怎么走

拿这段调用当例子：

```java
public static void main(String[] args) {
    int x = 3;
    int y = 7;
    int bigger = max(x, y);
    System.out.println(bigger);
}
```

执行顺序是这样：

1. 走到 `max(x, y)`，先算实参：`x` 的值 3、`y` 的值 7。实参可以是一个表达式，先算完再传。
2. 控制权交给 `max` 的方法体，同时为**这一次调用**准备一套全新的变量空间：`a` 是 3，`b` 是 7。
3. 方法体按顺序执行：`3 > 7` 为 false，跳过第一个 `return`；执行 `return b;`，把 7 交出去。
4. 这一步做完，`max` 这次调用用的变量（`a`、`b`）全部销毁，控制权回到调用点，7 正好落在原来写 `max(x, y)` 的位置上。
5. 继续执行调用点后面那一行：7 赋给 `bigger`。

```mermaid
flowchart TD
    A["调用点：max(x, y)"] --> B["先算出实参 3 和 7"]
    B --> C["进入方法体：新建形参 a=3、b=7"]
    C --> D{"走到 return 了吗？"}
    D -->|"是"| E["把值交出去，方法到此结束"]
    E --> F["销毁 a、b，回到调用点"]
    F --> G["继续执行调用点后面那一行"]
```

第 2 步和第 4 步是重点。JVM 规范把每次调用对应的这块空间叫**栈帧**（frame），里面装着这次调用的参数和局部变量；方法一返回，这块空间就整块丢掉 [4]。正因为是“新建一套”，方法里叫 `a`、外面叫 `x`，两个变量互不相干；也正因为如此，同一个方法在循环里被调用 100 次，每次拿到的都是一套干净的新变量。

还有一个容易忽略、但非常重要的推论：**方法在哪里被调用，执行完就回到哪里**。`max` 完全不知道调用它的那行代码写在 `main` 里、写在某个循环里，还是写在另一个方法里——它只负责把值交出去，剩下的交给调用点继续。

## 几个新手坑

**非 `void` 方法漏了 `return`。** 编译器报 missing return statement。判断标准是“所有路径”而不是“看起来会走到”：`if` 里 `return` 了，但没写 `else`，编译器仍然认为存在一条走到底的路。

**返回类型对不上。** 声明 `int` 却 `return` 一个 `double` 或 `boolean`，编译不过 [2]。

**以为改形参外面会跟着变。** 前面那个 `addOne` 已经演示过：基本类型形参是副本。

**调用时忘了写括号。** 只有 `max(3, 7)` 才是调用；光写一个 `max`，那只是方法的名字，程序不会执行它，编译器也会因为找不到这个变量而报错。

## 小结与预告

方法就是四件事：声明时写清返回类型、方法名、参数类型；调用时实参的个数、类型、顺序都要对上；形参是副本，在里面怎么改都影响不到外面；结果通过 `return` 交回，非 `void` 方法必须保证每条路径都有 `return`。

预告一句：名字相同、参数类型不同的两个方法可以同时存在，调用时 Java 按你给的实参挑一个执行，这叫作**重载**（overloading），下一篇专门讲它的规则。

## 术语表

- 方法：给一段完成特定任务的代码起个名字、打包成一个可以随时调用的单元，调用一次就执行一次。
- 形参与实参：形参是方法声明括号里那串变量，只在方法内部存在；实参是调用时真正传进去的值。
- 按值传递：调用时把实参的值复制一份给形参，方法内改形参不会影响调用处原来的变量。
- 返回类型：方法声明中标明它会交回哪种类型的值；不交回任何值就写 void。
- return 语句：把值交回调用点的同时立刻结束整个方法，后面的语句不会再执行。
- 栈帧：每次方法调用对应的一块独立内存空间，装着这次调用的参数和局部变量，方法返回后就整块丢弃。
- 重载：名字相同、参数类型不同的多个方法可以同时存在，调用时按实参决定用哪一个。

## 来源

1. [Oracle 官方教程 Defining Methods — 方法声明的组成部分与必需元素](https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html)
2. [Oracle 官方教程 Returning a Value from a Method — return 语句、void 与非 void 的规则](https://docs.oracle.com/javase/tutorial/java/javaOO/returnvalue.html)
3. [Oracle 官方教程 Passing Information to a Method or a Constructor — 形参与实参的区别、基本类型按值传递](https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html)
4. [JVM 规范第 2 章 — 栈帧在方法调用时创建、返回时销毁，局部变量存放参数](https://docs.oracle.com/en/java/javase/26/docs/specs/jvms/jvms-2.html)

---

原文：https://pangzhengboyin.com/articles/java-methods-parameters-return-values-a4a6d4d6

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
