Can I pass an array as arguments to a method with variable arguments in Java?
Asked Answered
L

6

367

I'd like to be able to create a function like:

class A {
  private String extraVar;
  public String myFormat(String format, Object ... args){
    return String.format(format, extraVar, args);
  }
}

The problem here is that args is treated as Object[] in the method myFormat, and thus is a single argument to String.format, while I'd like every single Object in args to be passed as a new argument. Since String.format is also a method with variable arguments, this should be possible.

If this is not possible, is there a method like String.format(String format, Object[] args)? In that case I could prepend extraVar to args using a new array and pass it to that method.

Lurleen answered 27/5, 2010 at 21:36 Comment(3)
I can't help but wonder why this question is a "is this valid" kind of question. Couldn't you have just tried it? Don't overdo asking, you'll do yourself more harm than good.Moise
true enough, this could have been easily tested. however, the nice thing about a question like this is that it exposes the topic and solicits interesting answers.Velour
I actually tried the code above, with the intention to pass the array args as arguments to the method. However, I didn't realize that I should've prepended extraVar to args first. When you know that variable arguments are treated as an array (even outside the method), this is of course quite logical.Lurleen
D
209

The underlying type of a variadic method function(Object... args) is function(Object[] args). Sun added varargs in this manner to preserve backwards compatibility.

So you should just be able to prepend extraVar to args and call String.format(format, args).

Disposure answered 27/5, 2010 at 21:58 Comment(1)
Passing an argument of type X[] into a method x(X... xs) gives the following warning in Eclipse: Type X[] of the last argument to method x(X...) doesn't exactly match the vararg parameter type. Cast to X[] to confirm the non-varargs invocation, or pass individual arguments of type X for a varargs invocation.Ogdan
S
388

Yes, a T... is only a syntactic sugar for a T[].

JLS 8.4.1 Format parameters

The last formal parameter in a list is special; it may be a variable arity parameter, indicated by an elipsis following the type.

If the last formal parameter is a variable arity parameter of type T, it is considered to define a formal parameter of type T[]. The method is then a variable arity method. Otherwise, it is a fixed arity method. Invocations of a variable arity method may contain more actual argument expressions than formal parameters. All the actual argument expressions that do not correspond to the formal parameters preceding the variable arity parameter will be evaluated and the results stored into an array that will be passed to the method invocation.

Here's an example to illustrate:

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 ]"
}

And yes, the above main method is valid, because again, String... is just String[]. Also, because arrays are covariant, a String[] is an Object[], so you can also call ezFormat(args) either way.

See also


Varargs gotchas #1: passing null

How varargs are resolved is quite complicated, and sometimes it does things that may surprise you.

Consider this example:

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!!!

Due to how varargs are resolved, the last statement invokes with objs = null, which of course would cause NullPointerException with objs.length. If you want to give one null argument to a varargs parameter, you can do either of the following:

count(new Object[] { null }); // prints "1"
count((Object) null); // prints "1"

Related questions

The following is a sample of some of the questions people have asked when dealing with varargs:


Vararg gotchas #2: adding extra arguments

As you've found out, the following doesn't "work":

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

Because of the way varargs work, ezFormat actually gets 2 arguments, the first being a String[], the second being a String. If you're passing an array to varargs, and you want its elements to be recognized as individual arguments, and you also need to add an extra argument, then you have no choice but to create another array that accommodates the extra element.

Here are some useful helper methods:

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;
}

Now you can do the following:

    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 gotchas #3: passing an array of primitives

It doesn't "work":

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

Varargs only works with reference types. Autoboxing does not apply to array of primitives. The following works:

    Integer[] myNumbers = { 1, 2, 3 };
    System.out.println(ezFormat(myNumbers));
    // prints "[ 1 ][ 2 ][ 3 ]"
Sixth answered 28/5, 2010 at 4:32 Comment(6)
Using Object... variadic parameters makes it difficult to use additional signatures as the compiler identifies ambiguity between the signatures where even a primitive argument can be autoboxed to Object.Elfrieda
There is a missing gotcha : if your method has a last param of type Object... and you call it passing a String[] for instance. The compiler doesn't know if your array is the first item of the varargs or is the equivalent of all the varargs. It will warn you.Auricular
Passing an argument of type X[] into a method x(X... xs) gives the following warning in Eclipse: Type X[] of the last argument to method x(X...) doesn't exactly match the vararg parameter type. Cast to X[] to confirm the non-varargs invocation, or pass individual arguments of type X for a varargs invocation.Ogdan
returning X[] to a method that that takes(X ... xs) ... i doubt it will be expanded.Commitment
Is there a convenience method in StringUtils that can handle gotcha #2, without having to add all the elements to a single input array argument? For instance: StringUtils.format(formatString, argArray, extraElement)Scheck
I suggests replacing new String(new char[args.length]) .replace("\0", "[ %s ]") with "[ %s ]".repeat(args.length) which works since Java 11, as the work-around is distracting from the actual point of the example.Falconry
D
209

The underlying type of a variadic method function(Object... args) is function(Object[] args). Sun added varargs in this manner to preserve backwards compatibility.

So you should just be able to prepend extraVar to args and call String.format(format, args).

Disposure answered 27/5, 2010 at 21:58 Comment(1)
Passing an argument of type X[] into a method x(X... xs) gives the following warning in Eclipse: Type X[] of the last argument to method x(X...) doesn't exactly match the vararg parameter type. Cast to X[] to confirm the non-varargs invocation, or pass individual arguments of type X for a varargs invocation.Ogdan
D
30

It's ok to pass an array - in fact it amounts to the same thing

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

is the same as

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

It's just syntactic sugar - the compiler converts the first one into the second, since the underlying method is expecting an array for the vararg parameter.

See

Dumortierite answered 27/5, 2010 at 22:0 Comment(0)
H
7

jasonmp85 is right about passing a different array to String.format. The size of an array can't be changed once constructed, so you'd have to pass a new array instead of modifying the existing one.

Object newArgs = new Object[args.length+1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = extraVar; 
String.format(format, extraVar, args);
Heartsome answered 28/5, 2010 at 4:19 Comment(0)
J
5

I was having same issue.

String[] arr= new String[] { "A", "B", "C" };
Object obj = arr;

And then passed the obj as varargs argument. It worked.

Jansson answered 19/12, 2017 at 13:52 Comment(1)
Please add that too.Knifeedged
A
0

In addition to #2 of @genelubricant - in case read-only is enough and if you want to swap a little extra effort for gaining some of the conveniences of a List - copying the array is not necessarily required and wrapping it, plus injecting the desired extra value at an arbitrary index at access time, can be achieved like this:

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];
    }
  };
}
Altruist answered 4/5, 2023 at 16:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.