ntalbs
ntalbs

Reputation: 29448

Is varargs of array possible in Java?

I tried to implement a method to merge any number of arrays.

@SuppressWarnings("unchecked")
public static <T> T[] merge(T[]... arrs) {
    int length = 0;
    for (T[] arr : arrs) {
        length += arr.length;
    }

    T[] merged = (T[]) new Object[length];
    int destPos = 0;
    for (T[] arr : arrs) {
        System.arraycopy(arr, 0, merged, destPos, arr.length);
        destPos += arr.length;
    }
    return merged;
}

It compiled, and looked no problem. Then I tested this method like this:

    String[] a = {"a", "b", "c"};
    String[] b = {"e", "f"};
    String[] c = {"g", "h", "i"};

    String[] m = merge(a,b,c);

Though this code successfully compiled, it throws an exception:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
    at ntalbs.test.ArrayMerge.main(ArrayMerge.java:25)

Code looks plausible and compiled successfully, but doesn't work and throws an exception at runtime. Could you explain why this code doesn't work? What am I missing?

Upvotes: 1

Views: 384

Answers (5)

pys
pys

Reputation: 91

You cannot cast an Object[] into a String[]. You need to create an instance of the correct runtime type. For that, you can use the java.lang.reflect.Array#newInstance method. To get the proper type, you can add a Class argument to the merge method as follows :

public static <T> T[] merge(final Class<T> arrayType, T[]... arrs) {
    int length = 0;
    for (T[] arr : arrs) {
        length += arr.length;
    }

    @SuppressWarnings("unchecked")
    T[] merged = (T[]) Array.newInstance(arrayType, length);
    int destPos = 0;
    for (T[] arr : arrs) {
        System.arraycopy(arr, 0, merged, destPos, arr.length);
        destPos += arr.length;
    }
    return merged;
}

and this can be executed without runtime exception:

    String[] a = { "a", "b", "c" };
    String[] b = { "e", "f" };
    String[] c = { "g", "h", "i" };

    String[] m = merge(String.class, a, b, c);

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 691705

The problem doesn't have anything to do with varargs.

As the error message says: your method creates and return an Object[], and you're casting it to String[]. But an Object[] is not an String[]. Hence the exception.

Look at the source code of Collection (and its T[] toArray() method), to understand how it can basically do what you're trying to do: create an array of type T[].

Upvotes: 5

D&#225;vid Horv&#225;th
D&#225;vid Horv&#225;th

Reputation: 4320

Yes, varargs works with every Java type. The above exception is generated by the line T[] merged = (T[]) new Object[length];, and has nothing to do with varargs.

Arrays are not designed to be generic. I recommend, use the Collections framework instead.

If you can not change your code structure, try something similar to this:

@SuppressWarnings("unchecked")
public static <T> T[] merge(T[]... arrays) {
    int fullSize = 0;
    for (T[] array: arrays) {
        fullSize += array.length;
    }
    List<T> concatenatedList = new ArrayList<T>(fullSize);
    for (T[] array: arrays) {
        concatenatedList.addAll(Arrays.asList(array));
    }
    Class<T> targetType = (Class<T>)arrays
        .getClass().getComponentType().getComponentType()
    ;
    return concatenatedList.toArray(
        (T[])Array.newInstance(targetType, concatenatedList.size())
    );
}

Upvotes: 0

Codebender
Codebender

Reputation: 14471

Your problem is not because of varargs.

It's because of the line,

T[] merged = (T[]) new Object[length];

An Object array cannot be cast to a String array.

Use,

T[] merged = (T[])java.lang.reflect.Array.newInstance(
                            arrs[0].getClass().getComponentType(), length);

Note:

As pointed out by @JB, this will only work if you try to merge the same array types. If you want to merge different array types, then you have to find the super type common to all of the arrays and use that in place of arrs[0].getClass().

Upvotes: 2

Chris Dennett
Chris Dennett

Reputation: 22721

You can create an array via T's class. Since you passed in objects, you can get the type from one of those. Something like this:

Class<T> clazz = arrs[0].getClass();
T[] arr = (T[]) Array.newInstance(clazz.getComponentType(), <length of all passed arrays>);

then just fill the array.

Upvotes: 2

Related Questions