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。执行流程:

  1. 为传递 ab,Java 需创建数组
  2. Object[] 可容纳任何类型,编译器创建 Object[]
  3. toArray 方法返回该 Object[]
  4. 调用方期望 String[],编译器尝试转换,导致异常

建议阅读 Joshua Bloch 的《Effective Java》第 32 条了解堆污染细节。

6. 总结

可变参数能大幅减少 Java 中的样板代码。

得益于其与数组的自动转换机制,还能增强代码的未来兼容性。

本文所有代码示例可在 GitHub 仓库获取。


原始标题:Varargs in Java