一个方法,任意数量的参数
有时你事先并不知道一个方法需要多少个参数。这一刻你可能想要 max(3, 9),下一刻又想要 max(1, 7, 4, 2, 8)。没有可变参数的话,你得为每种参数个数写一个重载,或者强迫调用者自己构建数组。可变参数(varargs,即 variable arguments 的缩写)让一个方法就能接收零个、一个或多个同类型的值。
声明时,在参数类型后面加上三个点(...):
调用者传入零散的值;在方法内部,numbers 是一个真正的数组。这就是全部思路:编译器替你把参数打包成一个数组。
可变参数就是数组
运行时并没有什么魔法。声明为 int... numbers 的参数,在方法内部就是一个 int[]。你可以获取它的 length、按索引访问,也可以像其他数组一样遍历它。
因为它确实是数组,你也可以把一个现成的数组传给可变参数方法,两种形式它都接受:
规则:放在最后,且只能有一个
有两条规则保证可变参数不会产生歧义,编译器会强制执行这两条:
- 可变参数必须放在最后。 普通参数在前,
...参数在后,接收剩下的所有值。 - 一个方法最多只能有一个可变参数。 如果有两个,就无法判断一个在哪里结束、下一个从哪里开始。
这段代码可以通过编译。但如果把可变参数放在前面,写成 static void log(Object... values, String level),就会出现编译错误:varargs parameter must be the last。必需的参数 level 总是在 ... 之前。
你早就在用可变参数了
其实你一直在调用可变参数方法。System.out.printf 和 String.format 就是经典例子,它们的第二个参数是 Object... args:
List.of(...)、Arrays.asList(...) 以及许多构建器风格的 API 也都是可变参数。这就是为什么你可以向它们传入任意数量的元素,而不用自己把它们包进数组。
可变参数与重载
可变参数和重载放在一起时,有一个让人意外的地方。当一个普通重载和一个可变参数重载都能匹配某次调用时,Java 会选择更具体的那个,也就是固定参数个数的方法胜出:
pick(1, 2) 两个都能匹配,但 Java 选择了非可变参数的重载,因为它更贴切。只有在没有固定参数个数的重载能匹配时,才会执行可变参数方法。这通常正是你想要的,但这也意味着,新增一个可变参数重载可能会悄悄改变某次调用实际调用的方法。
留意空调用和 null 调用
有两个坑经常让人中招。第一,不带任何参数调用可变参数方法时,方法收到的是一个空数组,而不是 null,所以 numbers.length 是 0,循环什么也不做。对于无参数的情况,你不需要做 null 检查:
第二,直接传入字面量 null 既有歧义又危险:Java 可能把 count(null) 理解为“整个可变参数数组是 null”,而不是“一个 null 元素”,于是在你读取它的 length 时抛出 NullPointerException。如果你真的想传一个 null 元素,就明确写成 count((Object) null)。
下一页:类
可变参数补全了方法参数的用法:在重载之外,还有灵活的参数列表。到目前为止,这些方法都只是用 static 松散地放在一个类里。接下来你将看到类真正的工作方式:把数据(字段)和行为(方法)打包成你自己的类型,这正是面向对象 Java 的核心。
常见问题
Java 中的可变参数是什么?
可变参数(varargs)让一个方法可以接收任意数量的同类型参数。声明时在类型后面加上 ...:int sum(int... numbers)。调用者可以传入零个、一个或多个值,而在方法内部,numbers 就是一个普通数组。它只是语法糖:Java 会替你把零散的参数收集到一个数组里。
Java 方法可以有多个可变参数吗?
不可以。一个方法最多只能有一个可变参数,而且它必须是参数列表中的最后一个,例如 void log(String level, Object... values)。如果它不在最后,Java 就无法判断可变长度的部分在哪里结束、下一个参数从哪里开始。普通参数总是放在 ... 参数之前。
Java 中可变参数和数组参数有什么区别?
在运行时二者几乎相同:可变参数 int... 在方法内部就是一个 int[]。区别在调用的地方:用可变参数时你写 sum(1, 2, 3)(零散的值),而用显式的数组参数时,你必须先构建数组再传入,sum(new int[]{1, 2, 3})。可变参数方法也能直接接收数组,所以它是更灵活的选择。