18

I am wondering if there is an easy, elegant and reusable way to pass a string and a string array to a method that expect varargs.

/**
 * The entry point with a clearly separated list of parameters.
 */
public void separated(String p1, String ... p2) {
    merged(p1, p2, "another string", new String[]{"and", "those", "one"});
}

/**
 * For instance, this method outputs all the parameters.
 */
public void merged(String ... p) {
    // magic trick
}

Even if all the types are consistent (String) I cannot find a way to tell to the JVM to flatten p2 and inject it to the merged parameter list?

At this point the only way is to create a new array, copy everything into it and pass it to the function.

Any idea?


EDIT

Base on your proposal here is the generic method I'll use:

/**
 * Merge the T and T[] parameters into a new array.
 *
 * @param type       the destination array type
 * @param parameters the parameters to merge
 * @param <T>        Any type
 * @return the new holder
 */
@SuppressWarnings("unchecked")
public static <T> T[] normalize(Class<T> type, Object... parameters) {
    List<T> flatten = new ArrayList<>();
    for (Object p : parameters) {
        if (p == null) {
            // hum... assume it is a single element
            flatten.add(null);
            continue;
        }
        if (type.isInstance(p)) {
            flatten.add((T) p);
            continue;
        }
        if (p.getClass().isArray() && 
            p.getClass().getComponentType().equals(type)) {
            Collections.addAll(flatten, (T[]) p);
        } else {
            throw new RuntimeException("should be " + type.getName() + 
                                             " or " + type.getName() + 
                                             "[] but was " + p.getClass());
        }
    }
    return flatten.toArray((T[]) Array.newInstance(type, flatten.size()));
}

normalize(String.class, "1", "2", new String[]{"3", "4", null}, null, "7", "8");
poussma
  • 7,033
  • 3
  • 43
  • 68
  • 3
    [JLS 15.12.2.4](http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.4) is quite specific on what we can pass as vararg . It won't accept/flatten arrays, varargs or collections as a part of variable arity parameter – default locale Mar 12 '13 at 09:44

6 Answers6

6

I don't think there is a way to have the scalar and the array implicitly flattened into a single array.

The cleanest solution I can think of is to use a helper function:

// generic helper function
public static<T> T[] join(T scalar, T[] arr) {
    T[] ret = Arrays.copyOfRange(arr, 0, arr.length + 1);
    System.arraycopy(ret, 0, ret, 1, arr.length);
    ret[0] = scalar;
    return ret;
}

public void separated(String p1, String ... p2) {
    merged(join(p1, p2));
}
NPE
  • 486,780
  • 108
  • 951
  • 1,012
2

Changing the signature of merge to:

public<T> void merged(T ... p) 

Will at least allow you to call merge without problems. You would have to deal with arrays of string as a parameter, though.

Sebastian
  • 5,177
  • 4
  • 30
  • 47
  • Also see http://stackoverflow.com/questions/2925153/can-i-pass-an-array-as-arguments-to-a-method-with-variable-arguments-in-java , last answer with 30 upvotes by polygenelubricants – Sebastian Mar 12 '13 at 09:55
0

I think creating a new array is the way to go.

For instance you could use ArrayUtils from Apache Commons to achieve that.

ArrayUtils.addAll(p2, p1);

Best regards, Thomas

rbento
  • 9,919
  • 3
  • 61
  • 61
Thomas Ernst
  • 11
  • 1
  • 5
0

Within separated, parameter p2 is a String[]. The merged method can take either a single String[] parameter or a sequence of Strings but not a mixture of the two, so I don't think you have much option other than to build a new array (which is what varargs does behind the scenes anyway).

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
0

From varargs doc

The three periods after the final parameter's type indicate that the final argument may be passed as an array or as a sequence of arguments.

It clearly states an array OR a sequence of arguments, therefore you have to flatten the array yourself, for instance as suggested by NPE.

ThanksForAllTheFish
  • 7,101
  • 5
  • 35
  • 54
0

I doubt you can do that, the Java compiler should translate merged(p1, p2) into merged ( p1, p2 [ 0 ], p2 [ 1 ] ... p2 [ p2.length ] ), but I don't think they've ever wanted to create such a smart compiler (it also would create problems if merged ( p1, p2 ) existed too).

You can do this instead (using pseudo-syntax):

import com.google.common.collect.Lists
import java.util.Arrays

separated ( String p1, String... p2 ) {
  merged ( Lists.asList ( p1, p2 ) )
}

merged ( List<String> p ) {
...
}

merged ( String ... p ) {
  merged ( Arrays.asList ( p )
}

Arrays is from the standard JDK, Lists is from Guava. Both asList() methods create views over the underlining arrays, not a copy, so there aren't many speed/memory concerns.

I don't think a simpler approach exists. Yes, copying into a new array, as suggested by others, is simpler, but time/memory consuming. If your arrays are small, it may happen that creating and accessing Lists can be slower than copying, but for larger arrays, copying will become an higher overhead.

I like the suggestions by Sebastian: you define a wrapper of merge() that flattens the array on the flight. This works well as long as merge() is not a 3rd party method and you can change it. Else, I'm doing something similar here.

zakmck
  • 2,715
  • 1
  • 37
  • 53
  • My need is to find a way to handle any combination. Something like `(String|String[])*` `Lists.asList` has a only a very limited set of combination – poussma Mar 12 '13 at 11:06
  • I see, you would need a List that flattens a vararg when get(i) is invoked. It would need to create a map from indexes to array objects, but at that point it wouldn't be much different than a copy, unless you expect very large objects inside it. – zakmck Mar 12 '13 at 11:21