方法定义、调用与重载
本教程共 100 篇 · 第 32 篇 · 更新于 2026-08-05 · 约 14 分钟阅读
本节目标:会亲手定义带参数和返回值的方法,搞懂方法签名到底由哪几部分组成,并能判断两个同名方法算不算合法重载。
上一章你已经知道类里面能放字段。光有数据还不够,数据得能”动”起来——这就是方法要干的事。
方法是把一段逻辑装进盒子,贴上名字。以后想用,喊名字就行,不用把代码再抄一遍。
为什么非要有方法
先看一段没有方法的代码有多难受:
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));
}
}
同样的”相加再打印”写了三遍。哪天要求改成打印”总和为:“,你得改三处,漏一处就是 bug。
把它抽成方法,改一处就够了。代码越长,这个优势越明显。
方法的完整骨架
一个方法的定义分五块:
修饰符 返回值类型 方法名(参数列表) {
// 方法体:真正干活的语句
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; // 把结果交回给调用者
}
}
逐块拆开说:
- 修饰符:
public表示谁都能调,private表示只有本类能调。第 36 章会把四种访问级别讲透。 - 返回值类型:承诺”我会还你一个什么”。写
int就必须返回一个整数,写String就必须返回字符串。 - 方法名:小驼峰命名,动词开头。
add、getName、isEmpty都是好名字。 - 参数列表:外界要交给我的原料,用逗号隔开,每个都要写类型。
- 方法体:花括号里的语句,方法真正干的活。
形参和实参别搞混
定义方法时写在括号里的 int a, int b,叫形式参数(形参)。它们只是占位符,方法没被调用时不占任何内存。
调用方法时真正塞进去的 3 和 5,叫实际参数(实参)。
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 语句,方法执行完自然结束
}
Warning
void不是”返回了一个空对象”,而是”压根不返回任何东西”。所以int x = calc.sayHello("小明");这行编译不过——你不能接住一个不存在的值。
return 在方法里承担两个职责,很多初学者只记住了第一个:
- 把值送回去:
return 表达式; - 立刻结束方法:执行到
return那一刻,方法后面的代码全部作废
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);
}
Tip编译器会检查”所有分支都有返回值”。如果你写
if (a > b) return a;就结束了,没有兜底的return,编译器会报missing return statement。别嫌它烦,它在帮你堵漏。
方法签名:判断重载的唯一标准
理解重载之前,得先认识**方法签名(signature)**这个概念。
Java 里一个方法的签名 = 方法名 + 参数类型列表(含顺序)。
不属于签名的东西有:返回值类型、参数名、访问修饰符、抛出的异常。
记住这句话,后面所有重载的规则都能自己推出来。
方法重载:同名不同参
同一个类里,方法名可以重复,只要签名不同就行。这叫方法重载(overload)。
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 对应 重要
}
}
重载的价值在于省记忆。System.out.println() 能打印 int、double、String、char、对象……全靠重载。要是每种类型一个名字,你得背 printlnInt、printlnDouble 一大串。
这两种情况不算重载
只改返回值类型不算:
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)
参数名对编译器来说是透明的,改了等于没改。
Warning上面两种写法编译期就报错,不是运行时才炸。所以你不会真把它带到线上——但面试时经常被问,值得记牢。
编译器怎么挑重载方法
传的实参和形参类型不完全一样时,编译器按三轮筛选,一轮命中就停:
- 精确匹配 + 自动拓宽:不考虑装箱、不考虑可变参数。
int可以喂给long/float/double。 - 允许自动装箱拆箱:还是不考虑可变参数。
int可以变成Integer。 - 允许可变参数:最后的兜底。
看个具体例子:
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
null 既能当 String 也能当 StringBuilder,两个都能匹配、又没有谁更具体,编译器直接摆烂报错。
解决办法是把类型明确写出来:
handle((String) null); // 明确告诉编译器走 String 版本
Note真遇到这种设计,更好的做法是改方法名,比如
handleText和handleBuffer。让调用方靠强制转型来消歧义,本身就是设计味道不对的信号。
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;
}
}
外部拿到 Person 对象只能调 getAge(),看不见 calcAge() 的存在。这个类甚至没有 age 字段,年龄是实时算出来的——外界完全不知情,也不需要知道。
这就是方法带来的隔离能力,第 36 章讲封装时会展开。
重载和重写别混淆
后面第 40 章会学到方法重写(override),名字和重载(overload)就差一个字母,含义完全不同:
- 重载发生在同一个类里,同名、参数列表不同,编译期就定好调哪个。
- 重写发生在父子类之间,同名、参数完全相同,运行期才定调哪个。
先记住这个区别,等学到继承时再回头看会更清楚。
小结
方法把一段逻辑打包成可复用的单元,骨架是”修饰符 + 返回类型 + 方法名 + 参数列表 + 方法体”。
return 既送值也终止方法,void 表示不返回任何东西。
方法签名只包含方法名和参数类型列表,这是判断重载是否合法的唯一依据——只改返回值或只改参数名都不算数。
编译器挑重载方法按”精确/拓宽 → 装箱 → 可变参数”三轮筛选,代价小的优先。下一章讲构造方法,你会发现它是一种很特殊的方法。