Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill an Array with generic Lists using supplier in Java 8 throws ClassCastEx b/c of type erasure

I want to fill an Array with generic Lists as Elements using a Supplier and Stream.generate.

Looks like this:

    Supplier<List<Object>> supplier = () -> new ArrayList<Object>();
    List<Object>[] test = (List<Object>[]) Stream.generate(supplier).limit(m).toArray();

With the Error output being:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.util.List;

Now how do I fill an Array with a generic type using the techniques provided by Java 8? Or is that simply not possible (yet) and I have to do it the "classic" way?

Regards, Claas M

EDIT

Upon @Water's request I did a bit of performance testing with filling Arrays/Lists using stream.collect (With a Cast testing Arrays) and the traditional Method of iterating.

First the performance tests using Lists:

private static int m = 100000;

/**
 * Tests which way is faster for LISTS.
 * Results:
 * 1k Elements: about the same time (~5ms)
 * 10k Elements: about the same time (~8ms)
 * 100k Elements: new way about 1.5x as fast (~18ms vs ~27ms)
 * 1M Elements: new way about 2x as fast (~30ms vs ~60ms)
 * NOW THIS IS INTERESTING:
 * 10M Elements: new way about .1x as fast (~5000ms vs ~500ms)
 * (100M OutOfMemory after ~40Sec)
 * @param args
 */

public static void main(String[] args) {

    Supplier<String> supplier = () -> new String();
    long startTime,endTime;

    //The "new" way
    startTime = System.currentTimeMillis();
    List<String> test1 =  Stream.generate(supplier).limit(m ).collect(Collectors.toList());
    endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime);


    //The "old" way
    startTime = System.currentTimeMillis();
    List<String> test2 = new ArrayList();
    Iterator<String> i = Stream.generate(supplier).limit(m).iterator();
    while (i.hasNext()) {
        test2.add(i.next());
    }
    endTime = System.currentTimeMillis();
    System.out.println(endTime - startTime);


}

And second the Performance tests using Arrays:

    private static int m = 100000000;

    /**
     * Tests which way is faster for ARRAYS.
     * Results:
     * 1k Elements: old way much faster (~1ms vs ~6ms)
     * 10k Elements: old way much faster (~2ms vs ~7ms)
     * 100k Elements: old way about 2x as fast (~7ms vs ~14ms)
     * 1M Elements: old way a bit faster (~50ms vs ~60ms)
     * 10M Elements: old way a bit faster (~5s vs ~6s)
     * 100M Elements: Aborted after about 5 Minutes of 100% CPU Utilisation on an i7-2600k
     * @param args
     */

    public static void main(String[] args) {

        Supplier<String> supplier = () -> new String();
        long startTime,endTime;

        //The "new" way
        startTime = System.currentTimeMillis();
        String[] test1 =  (String[]) Stream.generate(supplier).limit(m ).collect(Collectors.toList()).toArray(new String[m]);
        endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);


        //The "old" way
        startTime = System.currentTimeMillis();
        String[] test2 = new String[m];
        Iterator<String> it = Stream.generate(supplier).iterator();
        for(int i = 0; i < m; i++){
            test2[i] = it.next();
        }
        endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);


    }

}

As you can see, Water was indeed right - the Cast makes it slower. But for Lists the new Method is faster; at least from 100k - 1M Elements. I still dont know why its so much slower when it comes to 10M Elements and I'd really like to hear some comment on that.

like image 960
Claas M. Avatar asked May 21 '15 13:05

Claas M.


1 Answers

The Stream generator still generates the objects you want, the only problem is calling toArray() will give you back an object array, and you can't downcast from an Object array to a sub object array (since you've got something like: Object[] { ArrayList, ArrayList }).

Here is an example of what is happening:

You think you have this:

    String[] hi = { "hi" };
    Object[] test = (Object[]) hi; // It's still a String[]
    String[] out = (String[]) test;
    System.out.println(out[0]); // Prints 'hi'

But you actually have:

    String[] hi = { "hi" };
    Object[] test = new Object[1]; // This is not a String[]
    test[0] = hi[0];
    String[] out = (String[]) test; // Cannot downcast, throws an exception.
    System.out.println(out[0]);

You are getting back the immediate block above, which is why you're getting a casting error.

There's a few ways around it. If you want to go over your list, you could easily make an array out of them.

    Supplier<List<Integer>> supplier = () -> { 
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(5);
        a.add(8);
        return a;
    };

    Iterator<List<Integer>> i = Stream.generate(supplier).limit(3).iterator();

    // This shows there are elements you can do stuff with.
    while (i.hasNext()) {
        List<Integer> list = i.next();
        // You could add them to your list here.
        System.out.println(list.size() + " elements, [0] = " + list.get(0));
    }

If you are set on dealing with the function, you can do something like this:

    Supplier<List<Integer>> supplier = () -> { 
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(5);
        a.add(8);
        return a;
    };

    Object[] objArr = Stream.generate(supplier).limit(3).toArray();
    for (Object o : objArr) {
        ArrayList<Integer> arrList = (ArrayList<Integer>) o; // This is not safe to do, compiler can't know this is safe.
        System.out.println(arrList.get(0)); 
    }

According to the Stream Javadocs you can use the other toArray() method if you want to turn it into an array, but I've not explored this function yet so I don't want to discuss something I don't know.

like image 173
Water Avatar answered Nov 03 '22 17:11

Water