在Java中,我能否将数组作为参数传递给具有可变参数的方法?

362

我想创建一个类似以下的函数:

class A {
  private String extraVar;
  public String myFormat(String format, Object ... args){
    return String.format(format, extraVar, args);
  }
}
问题在于args在方法myFormat中被视为Object[],因此是String.format的单个参数,但我希望args中的每个Object作为新的参数传递。由于String.format也是一个具有可变参数的方法,这应该是可能的。

如果不可能,是否有像String.format(String format, Object[] args)这样的方法?在这种情况下,我可以使用新数组将extraVar添加到args并将其作为参数传递给该方法。


8
我不禁想知道为什么这个问题是一个“这是否有效”的问题。你为什么不试一试呢?不要过分询问,否则会自讨没趣。 - kamasheto
38
的确,这个问题本来可以轻易地进行测试。然而,这类问题的好处在于它揭示了主题,并引出了有趣的答案。 - akf
4
我实际上尝试了上述代码,打算将数组args作为参数传递给该方法。但是,我没有意识到应该先在args前面添加extraVar。当您知道可变参数被视为数组时(即使在方法之外)时,这当然是相当合理的。 - user362382
6个回答

383

是的,T...仅仅是T[]的一种语法糖。

JLS 8.4.1 格式化参数

列表中的最后一个形式参数是特殊的; 它可以是带有类型后跟省略号的可变元素参数。

如果最后一个形式参数是类型为T的可变元素参数,则被认为定义了一个类型为T[] 的形式参数。该方法然后是一个可变元素方法。否则,它是一个固定长度方法。调用可变元素方法可以包含比形式参数更多的实际参数表达式。不与变长参数之前的形式参数对应的所有实际参数表达式将被评估,并将结果存储到将传递给方法调用的数组中。

以下是一个例子来说明:

public static String ezFormat(Object... args) {
    String format = new String(new char[args.length])
        .replace("\0", "[ %s ]");
    return String.format(format, args);
}
public static void main(String... args) {
    System.out.println(ezFormat("A", "B", "C"));
    // prints "[ A ][ B ][ C ]"
}

是的,上面的 main 方法是有效的,因为再次提醒, String...只是 String[]。此外,由于数组是协变的, String[] Object[] ,因此您也可以以任何方式调用 ezFormat(args)

另请参阅


Varargs陷阱#1:传递null

如何解析varargs非常复杂,并且有时它会做出可能会让您感到惊讶的事情。

考虑以下示例:

static void count(Object... objs) {
    System.out.println(objs.length);
}

count(null, null, null); // prints "3"
count(null, null); // prints "2"
count(null); // throws java.lang.NullPointerException!!!

由于可变参数的解析方式,最后一个声明将使用 objs = null 调用,这显然会导致 NullPointerExceptionobjs.length。如果你想给可变参数传递一个 null 参数,则可以采取以下任一方法:
count(new Object[] { null }); // prints "1"
count((Object) null); // prints "1"

相关问题

以下是一些关于varargs的问题,这是人们在处理varargs时提出的一些问题样例:


Vararg陷阱#2:添加额外参数

正如您所发现的那样,以下操作无法“起作用”:

    String[] myArgs = { "A", "B", "C" };
    System.out.println(ezFormat(myArgs, "Z"));
    // prints "[ [Ljava.lang.String;@13c5982 ][ Z ]"

由于可变参数的工作方式,实际上 ezFormat 接收到了 2 个参数,第一个是 String[],第二个是 String。如果您将数组传递给可变参数,并且希望其元素被识别为单独的参数,并且还需要添加额外的参数,则只能创建另一个数组来容纳这个额外的元素。
以下是一些有用的辅助方法:
static <T> T[] append(T[] arr, T lastElement) {
    final int N = arr.length;
    arr = java.util.Arrays.copyOf(arr, N+1);
    arr[N] = lastElement;
    return arr;
}
static <T> T[] prepend(T[] arr, T firstElement) {
    final int N = arr.length;
    arr = java.util.Arrays.copyOf(arr, N+1);
    System.arraycopy(arr, 0, arr, 1, N);
    arr[0] = firstElement;
    return arr;
}

现在你可以做以下事情:
    String[] myArgs = { "A", "B", "C" };
    System.out.println(ezFormat(append(myArgs, "Z")));
    // prints "[ A ][ B ][ C ][ Z ]"

    System.out.println(ezFormat(prepend(myArgs, "Z")));
    // prints "[ Z ][ A ][ B ][ C ]"

Varargs陷阱#3:传递原始类型的数组

它不会“起作用”:

    int[] myNumbers = { 1, 2, 3 };
    System.out.println(ezFormat(myNumbers));
    // prints "[ [I@13c5982 ]"

Varargs只适用于引用类型。自动装箱不适用于原始类型数组。以下是可行的:

    Integer[] myNumbers = { 1, 2, 3 };
    System.out.println(ezFormat(myNumbers));
    // prints "[ 1 ][ 2 ][ 3 ]"

2
使用对象可变参数使得使用其他签名变得困难,因为编译器会在签名之间识别模糊性,即使一个原始参数可以自动装箱为对象。 - eel ghEEz
9
有一个易错点:如果你的方法有一个最后参数类型为Object...,并且你传递了一个String[],编译器无法确定你的数组是可变参数的第一个参数还是等同于所有可变参数。它会发出警告。 - Snicolas
将X[]返回到接受(X ... xs)的方法中...我怀疑它不会被扩展。 - mjs
1
我建议将 new String(new char[args.length]) .replace("\0", "[ %s ]") 替换为 "[ %s ]".repeat(args.length),这是自Java 11起可以工作的,因为绕过解决方案会分散对示例的实际重点。 - Holger
1
我建议将 new String(new char[args.length]) .replace("\0", "[ %s ]") 替换为 "[ %s ]".repeat(args.length),这个替换在 Java 11 及以上版本中有效,因为这个变通方法会分散注意力,不符合示例的实际目的。 - undefined
显示剩余2条评论

208

可变参数方法的基础类型为function(Object... args), 实际上等价于function(Object[] args)。 Sun公司采用这种方式添加可变参数是为了保持向后兼容。

因此,你只需要在args前面加上extraVar,然后调用String.format(format, args)即可。


6
在Eclipse中,将类型为X[]的参数传递给方法x(X... xs)会出现以下警告:方法x(X...)的最后一个参数的类型X[]与可变参数类型不完全匹配。强制转换为X[]以确认非可变参数调用,或传递类型为X的单个参数进行可变参数调用。 - Luke Hutchison

30

可以传递数组 - 实际上它等同于相同的操作

String.format("%s %s", "hello", "world!");

是相同的

String.format("%s %s", new Object[] { "hello", "world!"});

这只是语法糖 - 编译器将第一个转换为第二个,因为底层方法期望vararg参数是数组。
请参见:
- Varargs - J2SE 1.5文档

6

jasonmp85说的关于向String.format传递一个不同的数组是正确的。一旦构建了一个数组,它的大小就不能改变,因此您必须传递一个新的数组而不是修改现有数组。

Object newArgs = new Object[args.length+1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = extraVar; 
String.format(format, extraVar, args);

5
我也遇到了同样的问题。
String[] arr= new String[] { "A", "B", "C" };
Object obj = arr;

然后将obj作为可变参数传递。

它运行正常。


4
请添加那个也。 - Mathews Sunny

0
除了@genelubricant的第2点之外,如果只需要只读,并且想要交换一些额外的努力以获得一些List的便利性,则不一定需要复制数组,而是可以像这样包装它并在访问时在任意索引处注入所需的额外值:
public static <T> List<Object> wrappedUnmodfiableListInsertion(final T insert, final Integer insertIdx, final T... source) {
  final boolean isInsertion = insertIdx != null;
  return new AbstractList<Object>() {
    @Override
    public int size() {
      return source != null ? isInsertion ? source.length + 1 : source.length : 0;
    }
    @Override
    public Object get(int index) {
      if (source == null) {
        if (isInsertion) {
          return index == insertIdx && index == 0 ? insert : null;
        }
        return EMPTY_LIST;
      } 
      if (isInsertion) {
        if (index == insertIdx) {
          return insert;
        } 
        return source[insertIdx > index ? index : index - 1];
      }
      return source[index];
    }
  };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接