5.4 重载解析与 API 设计
前置知识:方法签名、参数与返回值
预计时间:20 分钟
完成标志:能判断重载是统一概念还是隐藏不同职责
编译器先看实参的静态类型
重载是同名方法采用不同参数列表。编译器先找不需要装箱和可变参数打包的适用方法,再考虑装箱,最后考虑可变参数调用;每个阶段还要选择最具体的方法,无法唯一选择就报歧义。
public class OverloadChoice {
static String pick(long value) { return "long"; }
static String pick(Integer value) { return "Integer"; }
static String describe(Object value) { return "object"; }
static String describe(String value) { return "string"; }
public static void main(String[] args) {
Object text = "hello";
System.out.println(pick(1));
System.out.println(describe(text));
System.out.println(describe("hello"));
}
}输出是 long、object、string。int 拓宽先于装箱;text 的静态类型是 Object,即使它引用字符串,也不会让编译器改选 describe(String)。两个互不继承的引用参数重载,例如 print(String) 和 print(Integer),遇到 print(null) 则可能歧义。
好重载保持同一语义
static int max(int left, int right) {
return left >= right ? left : right;
}
static long max(long left, long right) {
return left >= right ? left : right;
}两个方法都表示“返回两个同类型值中的较大者”,只是数值类型不同。
坏信号:同名方法一个保存文件,另一个发送邮件,只因参数碰巧不同。调用者看到名字无法预测副作用,应拆成 saveReport 与 emailReport。
委托避免实现漂移
static String describeItem(String name) {
return describeItem(name, 1, false);
}
static String describeItem(String name, int level) {
return describeItem(name, level, false);
}
static String describeItem(String name, int level, boolean rush) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("name is blank");
}
if (level < 1) {
throw new IllegalArgumentException("level: " + level);
}
return name + ":" + level + ":" + rush;
}校验和主逻辑集中在完整重载。避免两个重载互相调用形成递归环。
布尔参数常缺少可读性
describeItem("widget", 3, true);调用者很难看出 true 的含义。可把参数类型改为枚举或选项对象;下面展示改造后的调用形式,需要同时实现对应签名:
enum DeliverySpeed { STANDARD, RUSH }describeItem("widget", 3, DeliverySpeed.RUSH);重载爆炸提示参数对象
当可选项有 name、level、rush、color、engraving,每种组合都做重载会迅速膨胀。用不可变请求对象或 builder 表达组合,并在一个地方验证必填项。
这不是说“参数超过三个一定错误”。判断依据是组合数量、同类型参数误置风险、演进频率和调用可读性。
不要让重载偷偷改变单位
schedule(5); // 5 秒、毫秒还是天?
schedule(5L); // 只是类型变了,单位也变了吗?若不同重载采用不同单位,极易因整数字面量选择错误方法。使用 Duration 等带单位类型,或在名字中明确单位。
与重写区分
- 重载:同一作用域可见的同名、不同参数方法,编译期选择;
- 重写:子类为继承的实例方法提供实现,运行时按接收者对象选择。
两者可以同时出现,使调用更难推理。面向对象章节会用完整例子说明。
专业速查
| 设计信号 | 建议 |
|---|---|
| 同一概念、不同自然输入形状 | 可重载 |
| 副作用或语义不同 | 使用不同方法名 |
| 默认值 | 短重载委托完整重载 |
| 多个布尔或同类型参数 | 枚举、值对象或请求对象 |
| 单位不同 | 使用带单位类型,避免靠重载猜 |
| 组合指数增长 | builder 或参数对象 |
离开这一页前
拿一个重载组逐项检查:所有入口是否表达同一概念,默认值是否只在一处,布尔参数和单位是否会让调用者猜测,组合继续增长时是否该换成参数对象。重载在编译期选择,重写在运行时按接收者分派,两者不要混称。
规范入口:JLS §15.12.2.