1

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?

2
  • 5
    I think the problem is T[] merged = (T[]) new Object[length]; not the array varargs. Commented Apr 30, 2016 at 14:59
  • You are creating a new Object[] which cannot be cast to a String[] Commented Apr 30, 2016 at 15:13

5 Answers 5

5

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[].

Sign up to request clarification or add additional context in comments.

Comments

2

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

6 Comments

That will work only if all the input arrays are of the same type. If you merge a String[] and an Object[], it will try creating an array of Strings to hold objects.
Thanks @JBNizet ... Put a note to specify that.
@JBNizetthey have to be of the same type as per the signature of the merge method.
Use (Class<T>)arrs.getClass().getComponentType().getComponentType() to support empty parameter list.
@DávidHorváth: Plus it also supports when the arguments are of different array types, since arrs is guaranteed to be of a class that is a supertype of all of them.
|
2

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.

1 Comment

A small fix, it should be clazz.getComponentType() to the newInstance method. Otherwise you would get an array type (String[], instead of String) in T.
0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.