Reputation: 29448
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
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
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
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
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
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