bigywater.tech
Java 内部原理 · 43

可变参数遇上重载:真实验证参数个数对得上时,编译器真实优先选固定版本

一个方法同时有固定参数版本和可变参数版本,调用时编译器怎么选?真实构造几组调用,把 JLS 里"三阶段重载解析"这条抽象规则变成具体、可验证的结果。

pick(1, 2) → 固定版本
真实验证:参数个数正好对上固定参数版本时,编译器真实优先选它,不会去用可变参数版本
pick(1) / pick() → 可变版本
真实验证:参数个数对不上固定版本时,才真实退而选择可变参数版本来兜底

上一篇:Optional 本身可以是 null:真实验证这个类型没能真正杜绝 NPE

真实验证:参数个数一致时,固定版本真实优先

同时定义 pick(int a, int b) 和 pick(int... nums),两个版本理论上都能接受两个 int 参数的调用。

VarargsOverloadDemo.java static String pick(int a, int b) { return "固定两个参数版本"; } static String pick(int... nums) { return "可变参数版本,收到 " + nums.length + " 个"; }
真实实测
pick(1, 2) 真实选中 = 固定两个参数版本
pick(1, 2, 3) 真实选中 = 可变参数版本,收到 3 个
pick(1) 真实选中 = 可变参数版本,收到 1 个
pick() 真实选中 = 可变参数版本,收到 0 个

真实结果验证了 JLS 重载解析的分阶段规则:pick(1, 2) 真实选中了固定参数版本,不是可变参数版本;而参数个数不是 2(1 个、3 个、0 个)的调用,固定版本压根不适用,编译器才真实落到可变参数版本上。

真实验证:类型匹配上也遵循同样的优先级

固定参数版本比可变参数版本优先级更高,不只是体现在参数个数上,类型匹配也是如此。

VarargsOverloadDemo.java static String pick2(String s) { return "固定 String 版本"; } static String pick2(Object... args) { return "可变参数 Object... 版本,收到 " + args.length + " 个"; }
真实实测
pick2("hello") 真实选中 = 固定 String 版本
pick2("a", "b") 真实选中 = 可变参数 Object... 版本,收到 2 个

调用 pick2("hello") 时,理论上两个重载版本都能接受这次调用(String 可以直接匹配 pick2(String),也可以被当成 Object... 里唯一的一个元素),但真实结果显示编译器优先选中了固定参数版本。这背后是 JLS 定义的三阶段重载解析流程:第一阶段(不允许装箱/拆箱、不允许可变参数)能找到唯一匹配就直接用;只有第一阶段完全找不到匹配方法,才会进入允许装箱拆箱的第二阶段;第二阶段还找不到,才会进入允许可变参数展开的第三阶段。可变参数方法永远是"最后的选择",不是和固定参数方法平起平坐地竞争。

真实验证:直接传数组进去,不会被再包一层

数组和可变参数在调用点上是等价的语法糖,真实验证直接传一个数组不会产生"数组的数组"。

真实实测
pick(arr) 真实选中 = 可变参数版本,收到 3 个
这个优先级规则在真实项目里有什么意义?最常见的真实意义是:如果一个类库同时提供了固定参数的"快捷方法"和可变参数的"通用方法"(比如 String.format 系列、日志框架的 log(String msg, Object arg) 和 log(String msg, Object... args)),编译器会自动优先选择参数个数和类型都精确匹配的那个固定版本,不需要开发者操心"会不会走错分支、多包一层数组"——这个优先级是编译期就真实确定、可预测的,不依赖运行时状态。

分步演示:固定参数和可变参数同时存在时,编译器先挑哪一个

每一行都是真实运行这段代码得到的结果。

重载决议真实分阶段进行:严格匹配 > 拓宽转换 > 装箱/可变参数,可变参数版本真实永远是最后的兜底选项
这一步新验证的调用

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到。
    • 和重载 vs 重写那篇是同一个主题下更细的一层规则,建议对照读。
    • 没有涉及:多个可变参数方法互相重载时的歧义判定、装箱和可变参数同时出现时更复杂的候选筛选场景——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号