4

I have a utility class:

public class ArrayUtils {
    public static <T> T[] concat(T[]... arrays) {
        if(arrays == null) {
            return null;
        }
        int size = 0;
        for(T[] array : arrays) {
            size += array.length;
        }

        T[] concatenatedArray = (T[]) new Object[size];

        int i = 0;
        for(T[] array : arrays) {
            for(T item : array) {
                concatenatedArray[i++] = item;
            }
        }

        return concatenatedArray;
    }
}

When I test concat, it crushes:

public class ArrayUtilsTest extends TestCase {

    public void testConcat() throws Exception {
        Integer[] first = new Integer[]{1,2,3};
        Integer[] second = new Integer[]{4,5,6};
        Integer[] concat = ArrayUtils.concat(first, second);
        assertEquals(6, concat.length);
    }
}

with message

java.lang.ClassCastException: java.lang.Object[] cannot be cast to java.lang.Integer[]

I guess it has to do with generics. Could you provide suggestions on making it work? Also, background on the issue would be great.

Thanks!

2

5 Answers 5

2

Your problem is that you are creating an Object array in your concat method.

Instead of

T[] concatenatedArray = (T[]) new Object[size];

use

T[] concatenatedArray = (T[]) Array.newInstance(arrays[0].getClass()
        .getComponentType(), size);

UPDATE: While the above will probably do the job for you, newacct pointed out in the comments that it is even better/safer to use

T[] concatenatedArray = (T[]) Array.newInstance(arrays.getClass()
        .getComponentType().getComponentType(), size);

This will allow you to skip the size check and use the method with e.g. ArrayUtils.concat(new String[]{"foo"}, new Integer[]{42}); (if needed), which otherwise would fail with an ArrayStoreException.

You can ignore the type safety warning, because you know that the array is of type T, but you should make sure that you have at least one array.

You could do this e.g. by modifying your method signature to

public static <T> T[] concat(T[] array1, T[]... arrays);

As a short explanation: This has to do with how generic types are implemented in java, cf. https://docs.oracle.com/javase/tutorial/java/generics/genMethods.html.

Actually your first cast in concat should fail as well (because in this case you are already trying to cast an Object[] to an Integer[]), but due to type erasure, the byte code will contain a cast to Object[] instead (you should be ably to verify this by looking at the byte code or using a decompiler).

On the other hand, your test method is not generic, but explicitly casts to Integer[] (even in the byte code ;)), so it will fail.

Last but not least as a general rule of thumb: If possible, try to use existing libraries like Apache Commons instead of reinventing the wheel. You will find that most of your problems were already solved in the past by someone else ;)

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

6 Comments

Thanks. Would you mind elucidating why it doesn't work the old way?
@midnight You were essentially trying to do: Integer[] concatenatedArray = (Integer[]) new Object[size];, which is not possible. (an array of Object is not necessarily an array of Integer). If you try doing this without generics, you will have a compiler error.
This will not work in general. arrays[0] could be of runtime class U[], where U is a subtype of T, or arrays[0] could be nil, or arrays could be empty. So arrays[0].getClass().getComponentType() does not always get what you want.
@newacct: It works correctly for e.g. concat(Number[], Integer[]) (which should be your first case), there was already a NullPointerException before if any of the arrays is null (which should be your second case) and I advised to modify the signature to guard against arrays.size() == 0 (which should be your third case). What am I missing?
@Marvin: Given your "instead of ... use ..." modification to the OP's code, this does not work: ArrayUtils.concat(new String[]{"foo"}, new Integer[]{42});
|
2

You can get the T from the actual arrays array-of-arrays parameter, like this:

T[] concatenatedArray = (T[]) Array.newInstance(arrays.getClass()
    .getComponentType().getComponentType(), size);

Comments

1

You may simplify your code by using what the JRE already offers:

public static <T> T[] concat(T[] first, T[]... arrays) {
    int size = first.length;
    for(T[] array : arrays) size += array.length;
    if(size==first.length) return first.clone();
    ArrayList<T> list=new ArrayList<>(size);
    Collections.addAll(list, first);
    for(T[] array: arrays) Collections.addAll(list, array);
    return list.toArray(first);
}

Calling toArray on a List providing an appropriately type array will create a new array of that type if the size is bigger than the size of the provided array. We protect against the special case, that the size is not bigger than the first array, which implies that all other arrays are empty, by checking for that case first and simply returning a clone of the first array then.

But in most cases you are better off using the List instead of dealing with arrays…

Comments

0

you have to specify the Class for which the Array is to be created

T[] concatenatedArray = (T[])Array.newInstance(arrays[0][0].getClass(), size);

This code will get the first array from the T[]... arrays and get the class type of the first element, which will be used to create the Final Array

3 Comments

@marvin code is more generic.
This will not work in general. arrays[0][0] could be of runtime class U, where U is a subtype of T, or arrays[0][0] could be nil, or arrays[0] could be nil or empty or arrays could be empty. So arrays[0][0].getClass() does not always work.
Yes I got it thanks for the insight. Your sol is the most generic.
-1

public class ArrayUtilsTest extends TestCase {

public void testConcat() throws Exception {
    Integer[] first = new Integer[]{1,2,3};
    Integer[] second = new Integer[]{4,5,6};
//YOU need to cast the returned array from concat to an "Integer[]"
    Object[] a = ArrayUtils.concat(first, second);
    Integer[] concat = Arrays.copyOf(a, a.length, Integer[].class);

    assertEquals(6, concat.length);
}

}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.