简体   繁体   中英

Java generic varargs method parameters

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");

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

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.

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

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).

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM