Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

casting ArrayList.toArray() with ArrayList of Generic Arrays

A difficult question which I'm close to giving up all hope on. I'm trying to make a function, but am having problems getting ArrayList.toArray() to return the type I want.

Here's the minimal example that demonstrates my problem:

public static <T> T[] test(T one, T two) {
    java.util.List<T> list = new ArrayList<T>();
    list.add(one);
    list.add(two);
    return (T[]) list.toArray();
}

Normally I'd be able to use the form (T[]) list.toArray(new T[0]) but there are two added difficulties:

  1. Because of the covariance rules I cannot typecast arrays, (T[]) myObjectArray gives a ClassCastException
  2. You cannot create a new instance of a generic type, meaning I cannot instance new T[]. Nor can I use clone on one of the elements of ArrayList or try to get its class.

I've been trying to get some information using the following call:

public static void main(String[] args) {
   System.out.println(test("onestring", "twostrings"));
}

the result is of the form [Ljava.lang.Object;@3e25a5 suggesting that the typecast on the return is not effective. strangely the following:

public static void main(String[] args) {
    System.out.println(test("onestring", "twostrings").getClass());
}

comes back with:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at patchLinker.Utilities.main(Utilities.java:286)

So my best guess is that it think's it's a String array label wise, but internally is an Object array, and any attempts to access brings that inconsistancy out.

If anyone can find any way of working around this (since the two normal workarounds are denied to me) I'd be very greatful.

K.Barad JDK1.6


Edit


Thanks to Peter Lawrey for solving the initial problem. Now the issue is how to make the solution apply to my less trivial example:

public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;
    T[] typeVar;
    for (T[] subArray : input) {
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }
    if (input.length > 0) {
        typeVar = input[0];
    } else {
        return null;
    }
    return (T[]) outList.toArray((T[]) java.lang.reflect.Array.newInstance(typeVar.getClass(),outList.size()));
}

public static void main(String[] args) {
    String[][] lines = { { "111", "122", "133" }, { "211", "222", "233" } };
    unTreeArray(lines);
}

The result at the moment is

Exception in thread "main" java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at java.util.ArrayList.toArray(Unknown Source)
at patchLinker.Utilities.unTreeArray(Utilities.java:159)
at patchLinker.Utilities.main(Utilities.java:301)

I'm still doing some tests to see if I can get any more useful information than this, but at the moment I'm not sure where to start. I'll add any more information I get as I get it.

K.Barad


edit 2


Some more information now. I've tried to build the array manually and transfer the items in elementwise, so I'd be typecasting as T, rather than T[]. I found an interesting result:

public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;
    T[] outArray;
    for (T[] subArray : input) {
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }
    if (outList.size() == 0) {
        return null;
    } else {
        outArray = input[0];// use as a class sampler
        outArray =
                (T[]) java.lang.reflect.Array.newInstance(outArray.getClass(), outList.size());
        for (int i = 0; i < outList.size(); i++) {
            System.out.println(i + "  " + outArray.length + "   " + outList.size() + "   "  + outList.get(i));
            outArray[i] = (T) outList.get(i);
        }
        System.out.println(outArray.length);
        return outArray;
    }
}

I still get the following output:

0  6   6   111
Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
at patchLinker.Utilities.unTreeArray(Utilities.java:291)
at patchLinker.Utilities.main(Utilities.java:307)

Does this mean you can't write to a T[] even if you manage to create one indirectly?

like image 221
K.Barad Avatar asked Jan 19 '11 14:01

K.Barad


3 Answers

toArray() always returns an object array.

To return an array of a specific type, you need to use toArray(Object[]).

To provide the correct type for the array argument, you need to use the specific class or in this case use reflections.

try

 return (T[]) list.toArray(Array.newInstance(one.getClass(), list.size()));
like image 166
Peter Lawrey Avatar answered Oct 29 '22 23:10

Peter Lawrey


The problem on your second code sample is caused because of your T[] typeVar: Input is a two dim array, so input[0] will return a one dim array (a String[], instead a String, as expected).

If your are to convert your List<T> to a T[], you'll need a T typeVar,

To fix it:

public static <T> T[] unTreeArray(T[][] input) {
    java.util.List<T> outList = new ArrayList<T>();
    java.util.List<T> tempList;

    if (input.length == 0) {
        return null;
    }


    T typeVar=null;
    for (T[] subArray : input) {
        if(typeVar==null && subArray.length>0) {
            typeVar=subArray[0];
        }
        tempList = java.util.Arrays.asList(subArray);
        outList.addAll(tempList);
    }

    return outList.toArray((T[]) Array.newInstance(typeVar.getClass(),0));
}

public static void main(String[] args) {
    String[][] lines = { { "111", "122", "133" }, { "211", "222", "233" } };
    String[] result=unTreeArray(lines);

    System.out.println(result);

    System.out.println(result.getClass());

        //added for completion:
    System.out.println( Arrays.toString(result));

}

The resulting output:

[Ljava.lang.String;@5a405a4 class
[Ljava.lang.String;
[111, 122, 133, 211, 222, 233]

like image 44
Tomas Narros Avatar answered Oct 29 '22 21:10

Tomas Narros


You can't create an array of a generic type parameter. The simple reason is the fact that java usually erases the generic type information at runtime so the runtime has no idea what T is (when T is a generic type parameter).

So the solution is to actually get a hold of the type information at runtime. Usually this is done by passing around a Class<T> type parameter but in this case you can avoid that:

@peter's answer looks good and i would use it :).

return (T[]) list.toArray(Array.newInstance(one.getClass(), list.size()));
like image 22
Mihai Toader Avatar answered Oct 29 '22 21:10

Mihai Toader