1. 简介
可变参数(Varargs)是 Java 5 引入的特性,为支持任意数量同类型参数的方法提供了简洁语法。
本文将深入探讨如何使用这个核心 Java 特性。
2. 可变参数出现之前
在 Java 5 之前,当需要传递任意数量参数时,我们只能:
- 将所有参数封装在数组中传递
- 或为每种参数组合重载 N 个方法(每个新增参数对应一个方法):
public String format() { ... }
public String format(String value) { ... }
public String format(String val1, String val2) { ... }
3. 可变参数的使用
可变参数通过引入新语法帮我们避免样板代码,底层自动使用数组处理任意数量参数。
定义方式:标准类型声明后跟省略号:
public String formatWithVarArgs(String... values) {
// ...
}
现在可以这样调用方法,参数数量任意:
formatWithVarArgs();
formatWithVarArgs("a", "b", "c", "d");
⚠️ 重要提示:可变参数本质是数组,需按普通数组方式操作。
4. 使用规则
可变参数使用简单,但需遵守两条铁律:
- 每个方法只能有一个可变参数
- 可变参数必须是方法的最后一个参数
5. 堆污染问题
使用可变参数可能导致堆污染。看这个危险示例:
static String firstOfFirst(List<String>... strings) {
List<Integer> ints = Collections.singletonList(42);
Object[] objects = strings;
objects[0] = ints; // 堆污染发生
return strings[0].get(0); // 抛出 ClassCastException
}
测试调用:
String one = firstOfFirst(Arrays.asList("one", "two"), Collections.emptyList());
assertEquals("one", one);
❌ 结果:即使代码中没显式类型转换,仍会抛出 ClassCastException
:
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
5.1. 安全使用
每次使用可变参数时,Java 编译器都会创建数组存放参数。当与泛型结合时,编译器会警告潜在风险:
warning: [varargs] Possible heap pollution from parameterized vararg type T
✅ 安全使用条件(必须同时满足):
- 不向隐式创建的数组存储任何内容(上例错误地存入了
List<Integer>
) - 不让数组引用逃逸出方法(后续详述)
若确认方法安全使用可变参数,可用 @SafeVarargs
注解抑制警告。
简单说:仅当可变参数用于传递参数而不做其他操作时才安全!
5.2. 可变参数引用逃逸
再看另一个危险用法:
static <T> T[] toArray(T... arguments) {
return arguments;
}
表面无害,但违反了安全使用第二条规则——让可变参数数组逃逸到调用方。
看它如何引发问题:
static <T> T[] returnAsIs(T a, T b) {
return toArray(a, b);
}
调用时:
String[] args = returnAsIs("One", "Two");
❌ 再次触发 ClassCastException
。执行流程:
- 为传递
a
和b
,Java 需创建数组 - 因
Object[]
可容纳任何类型,编译器创建Object[]
toArray
方法返回该Object[]
- 调用方期望
String[]
,编译器尝试转换,导致异常
建议阅读 Joshua Bloch 的《Effective Java》第 32 条了解堆污染细节。
6. 总结
可变参数能大幅减少 Java 中的样板代码。
得益于其与数组的自动转换机制,还能增强代码的未来兼容性。
本文所有代码示例可在 GitHub 仓库获取。