user1467885
user1467885

Reputation: 113

Java create array from generic runtime type of another array

I have the following code which merges two arrays and should handle any type apart from primitives.

@SuppressWarnings("unchecked")
public static synchronized <E> E[]  aryMergeNoDup(E[]... arys){
    HashSet<E> hs = new HashSet<E>();
        for(E[] ary : arys) {
            if(ary == null) continue;
            for(E item : ary) {
                if(item != null) hs.add(item);
            }
        }
        hs.remove(null);
        return hs.toArray((E[]) Array.newInstance(
                arys.getClass().getComponentType(),hs.size()));
}

However when the code runs it generates this exception:

java.lang.ArrayStoreException: java.lang.String
  at java.util.AbstractCollection.toArray(AbstractCollection.java:188)
  at util.Utils.aryMergeNoDup(Utils.java:197)

The runtime type of the variable arys is String[]; however, when I replace arys.getClass().getComponentType() with String.class the code runs fine.

However, the method can only be used for Strings because pf this. I can't see what's going wrong, as they should both refer to java.lang.String.

The line in AbstractCollection.java:188 that throws the Exception is:

r[i] = (T)it.next();

public <T> T[] toArray(T[] a) {
    // Estimate size of array; be prepared to see more or fewer elements
    int size = size();
    T[] r = a.length >= size ? a :
              (T[])java.lang.reflect.Array
              .newInstance(a.getClass().getComponentType(), size);
    Iterator<E> it = iterator();

    for (int i = 0; i < r.length; i++) {
        if (! it.hasNext()) { // fewer elements than expected
            if (a != r)
                return Arrays.copyOf(r, i);
            r[i] = null; // null-terminate
            return r;
        }
        r[i] = (T)it.next();
    }
    return it.hasNext() ? finishToArray(r, it) : r;
}

Upvotes: 2

Views: 626

Answers (3)

BeeOnRope
BeeOnRope

Reputation: 64895

You've got two separate things interacting here. First, you are using varargs, which under the covers wrap their arguments in an array generated on the fly. So the type of arys will be E[][], so get component type will be E[]. Second, generics mean that E is erased to Object at runtime, so even two getComponentType calls aren't going to cut it - unless you are OK with always returning Object[].

What you could do, is use the component type of arys[0], if it exists. This won't even work for all cases, since for example the type of the second array may be a superclass, or a sibling of the first, assignment incompatible with an array of the first type.

To solve this, you could calculate the least upper bound type by examining the types of all the arrays, but I think this is overkill versus "first type wins" if your typical usage will be arrays of the same type.

Upvotes: 0

Ian Roberts
Ian Roberts

Reputation: 122364

Since it's a varargs, the runtime type of arys will be E[][], not E[]. Therefore you probably need arys.getClass().getComponentType().getComponentType() as the argument to Array.newInstance.

Upvotes: 3

gd1
gd1

Reputation: 11403

I believe the problem is that arys is something like an array of arrays. So probably arys.getClass().getComponentType() is String[], and not String.

Upvotes: 0

Related Questions