首页 / Java 入门教程 / 方法定义、调用与重载

Java 入门教程

方法定义、调用与重载

本教程共 100 篇 · 第 32 篇 · 更新于 2026-08-05 · 约 14 分钟阅读

JavaJava 入门教程方法方法重载方法签名return

32. 方法定义、调用与重载

本节目标:会亲手定义带参数和返回值的方法,搞懂方法签名到底由哪几部分组成,并能判断两个同名方法算不算合法重载。

上一章你已经知道类里面能放字段。光有数据还不够,数据得能”动”起来——这就是方法要干的事。

方法是把一段逻辑装进盒子,贴上名字。以后想用,喊名字就行,不用把代码再抄一遍。

为什么非要有方法

先看一段没有方法的代码有多难受:

public class Bad {
    public static void main(String[] args) {
        int a = 3, b = 5;
        System.out.println("结果是:" + (a + b));

        int c = 10, d = 20;
        System.out.println("结果是:" + (c + d));

        int e = 7, f = 8;
        System.out.println("结果是:" + (e + f));
    }
}
```bash

同样的"相加再打印"写了三遍。哪天要求改成打印"总和为:",你得改三处,漏一处就是 bug。

把它抽成方法,改一处就够了。代码越长,这个优势越明显。

## 方法的完整骨架

一个方法的定义分五块:

```java
修饰符 返回值类型 方法名(参数列表) {
    // 方法体:真正干活的语句
    return 返回值;   // 返回值类型是 void 时可省略
}

对照一个能跑的例子看:

public class Calculator {

    // public: 修饰符  int: 返回值类型  add: 方法名  (int a, int b): 参数列表
    public int add(int a, int b) {
        int sum = a + b;   // 方法体
        return sum;        // 把结果交回给调用者
    }
}
```bash

逐块拆开说:

- **修饰符**:`public` 表示谁都能调,`private` 表示只有本类能调。第 36 章会把四种访问级别讲透。
- **返回值类型**:承诺"我会还你一个什么"。写 `int` 就必须返回一个整数,写 `String` 就必须返回字符串。
- **方法名**:小驼峰命名,动词开头。`add`、`getName`、`isEmpty` 都是好名字。
- **参数列表**:外界要交给我的原料,用逗号隔开,每个都要写类型。
- **方法体**:花括号里的语句,方法真正干的活。

## 形参和实参别搞混

定义方法时写在括号里的 `int a, int b`,叫**形式参数**(形参)。它们只是占位符,方法没被调用时不占任何内存。

调用方法时真正塞进去的 `3` 和 `5`,叫**实际参数**(实参)。

```java
public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        int result = calc.add(3, 5);   // 3 和 5 是实参
        System.out.println(result);    // 输出 8
    }
}

调用的写法是 对象.方法名(实参)calc.add(3, 5) 读作”让 calc 这个对象去执行 add,把 3 和 5 交给它”。

实参的个数、类型、顺序必须和形参对得上。少传一个、类型对不上,编译器直接报错,根本跑不起来。

Note

形参名叫 a 还是 x,对调用方毫无影响。调用方只关心”要几个、什么类型”。所以形参名怎么起,纯粹是给读代码的人看的。

返回值与 void

方法有两类:一类要还东西,一类只干活不还东西。

只干活的那类,返回值类型写 void

public void sayHello(String name) {
    System.out.println("你好," + name + "!");
    // 没有 return 语句,方法执行完自然结束
}
```java

> [!WARNING]
> `void` 不是"返回了一个空对象",而是"压根不返回任何东西"。所以 `int x = calc.sayHello("小明");` 这行编译不过——你不能接住一个不存在的值。

`return` 在方法里承担两个职责,很多初学者只记住了第一个:

1. **把值送回去**:`return 表达式;`
2. **立刻结束方法**:执行到 `return` 那一刻,方法后面的代码全部作废

```java
public int max(int a, int b) {
    if (a > b) {
        return a;   // 命中这行,下面的 return b 永远不会执行
    }
    return b;
}

void 方法里也能写 return;(后面不跟值),用途只有一个——提前结束:

public void printName(String name) {
    if (name == null) {
        return;   // 名字是空的,直接退出,不往下走
    }
    System.out.println(name);
}
```bash

> [!TIP]
> 编译器会检查"所有分支都有返回值"。如果你写 `if (a > b) return a;` 就结束了,没有兜底的 `return`,编译器会报 `missing return statement`。别嫌它烦,它在帮你堵漏。

## 方法签名:判断重载的唯一标准

理解重载之前,得先认识**方法签名(signature)**这个概念。

Java 里一个方法的签名 = **方法名 + 参数类型列表(含顺序)**

不属于签名的东西有:返回值类型、参数名、访问修饰符、抛出的异常。

记住这句话,后面所有重载的规则都能自己推出来。

## 方法重载:同名不同参

同一个类里,方法名可以重复,只要**签名不同**就行。这叫**方法重载(overload)**

```java
public class Printer {

    public void show(int n) {
        System.out.println("整数:" + n);
    }

    public void show(String s) {          // 参数类型不同,合法
        System.out.println("字符串:" + s);
    }

    public void show(int a, int b) {      // 参数个数不同,合法
        System.out.println("两个整数:" + a + ", " + b);
    }

    public void show(String s, int n) {   // 顺序不同也算不同,合法
        System.out.println(s + " 重复 " + n + " 次");
    }

    public void show(int n, String s) {   // 与上一个顺序相反,仍然合法
        System.out.println(n + " 对应 " + s);
    }
}

调用时不用管有几个版本,正常写就行,编译器自己对号入座:

public class Main {
    public static void main(String[] args) {
        Printer p = new Printer();
        p.show(42);              // 整数:42
        p.show("你好");           // 字符串:你好
        p.show(1, 2);            // 两个整数:1, 2
        p.show("重要", 3);        // 重要 重复 3 次
        p.show(3, "重要");        // 3 对应 重要
    }
}
```bash

重载的价值在于**省记忆**。`System.out.println()` 能打印 intdouble、String、char、对象……全靠重载。要是每种类型一个名字,你得背 `printlnInt`、`printlnDouble` 一大串。

## 这两种情况不算重载

**只改返回值类型不算:**

```java
public int calc(int a) { return a; }
public double calc(int a) { return a; }   // 编译错误

原因很直白:签名里没有返回值。而且调用 calc(1); 时如果不接收结果,编译器根本无从判断你要哪个。

只改参数名不算:

public void set(int width) { }
public void set(int height) { }   // 编译错误,签名都是 set(int)
```bash

参数名对编译器来说是透明的,改了等于没改。

> [!WARNING]
> 上面两种写法编译期就报错,不是运行时才炸。所以你不会真把它带到线上——但面试时经常被问,值得记牢。

## 编译器怎么挑重载方法

传的实参和形参类型不完全一样时,编译器按三轮筛选,一轮命中就停:

1. **精确匹配 + 自动拓宽**:不考虑装箱、不考虑可变参数。`int` 可以喂给 `long`/`float`/`double`。
2. **允许自动装箱拆箱**:还是不考虑可变参数。`int` 可以变成 `Integer`。
3. **允许可变参数**:最后的兜底。

看个具体例子:

```java
public class Pick {

    public static void f(long x) {
        System.out.println("命中 long");
    }

    public static void f(Integer x) {
        System.out.println("命中 Integer");
    }

    public static void f(int... x) {
        System.out.println("命中可变参数");
    }

    public static void main(String[] args) {
        f(10);   // 输出:命中 long
    }
}

10 这个 int,第一轮里 long 版本靠自动拓宽就能接住,比赛提前结束。装箱版和可变参数版连出场机会都没有。

Tip

这个顺序的设计意图是”代价从小到大”:拓宽最便宜,装箱要建对象贵一点,可变参数还要现场造数组最贵。编译器永远挑最省事的那条路。

传 null 的歧义坑

重载最容易翻车的地方是传 null

public void handle(String s) { }
public void handle(StringBuilder sb) { }

// 调用
handle(null);   // 编译错误:reference to handle is ambiguous
```text

`null` 既能当 `String` 也能当 `StringBuilder`,两个都能匹配、又没有谁更具体,编译器直接摆烂报错。

解决办法是把类型明确写出来:

```java
handle((String) null);   // 明确告诉编译器走 String 版本
Note

真遇到这种设计,更好的做法是改方法名,比如 handleTexthandleBuffer。让调用方靠强制转型来消歧义,本身就是设计味道不对的信号。

private 方法:只给自己用

不是所有方法都得对外开放。类内部的辅助逻辑,用 private 挡住:

public class Person {
    private int birthYear;

    public void setBirthYear(int year) {
        this.birthYear = year;
    }

    public int getAge() {
        return calcAge(2026);   // 内部可以自由调用 private 方法
    }

    private int calcAge(int currentYear) {
        return currentYear - this.birthYear;
    }
}
```bash

外部拿到 `Person` 对象只能调 `getAge()`,看不见 `calcAge()` 的存在。这个类甚至没有 `age` 字段,年龄是实时算出来的——外界完全不知情,也不需要知道。

这就是方法带来的隔离能力,第 36 章讲封装时会展开。

## 重载和重写别混淆

后面第 40 章会学到**方法重写(override)**,名字和重载(overload)就差一个字母,含义完全不同:

- **重载**发生在**同一个类**里,同名、参数列表不同,编译期就定好调哪个。
- **重写**发生在**父子类**之间,同名、参数完全相同,运行期才定调哪个。

先记住这个区别,等学到继承时再回头看会更清楚。

## 小结

方法把一段逻辑打包成可复用的单元,骨架是"修饰符 + 返回类型 + 方法名 + 参数列表 + 方法体"

`return` 既送值也终止方法,`void` 表示不返回任何东西。

方法签名只包含**方法名和参数类型列表**,这是判断重载是否合法的唯一依据——只改返回值或只改参数名都不算数。

编译器挑重载方法按"精确/拓宽 → 装箱 → 可变参数"三轮筛选,代价小的优先。下一章讲构造方法,你会发现它是一种很特殊的方法。