8

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.

Claas M.
  • 267
  • 3
  • 11
  • 1
    For the performance tests I suggest following some of the guidelines here: https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java otherwise you may get very inaccurate results – SamYonnou May 22 '15 at 15:47
  • I know this is a very bad performace test. However, it was never intended to be a Benchmark giving exact results but only to tell which method of filling arrays is faster. I didn't want to spend to much time on it. – Claas M. May 22 '15 at 15:59

2 Answers2

4

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.

Water
  • 3,245
  • 3
  • 28
  • 58
  • Thanks for the Response! Your solutions all work, but they are all using the "traditional" method and I suppose they are slower than using streams. toArray with an Array as parameter sadly doesnt work with generic types :( ("Cannot create a generic array of List") I think I will stick to traditional ways then. – Claas M. May 21 '15 at 16:06
  • 1
    @ClaasM. Are you sure something is faster? I would guess that if you took the iterator and went over the streamed list it should be pretty quick rather than allocating everything into a new object array, casting...etc, if you were able to make a test (or rather, new post here) to show performance... that would be really cool! I'd be glad to learn how Java's performance of Stream/Supplier/..etc, does versus the standard boring old way we're all used to. – Water May 22 '15 at 01:46
  • 1
    I edited my question including the findings. Please take a look if you have the time, – Claas M. May 22 '15 at 15:35
  • 1
    @ClaasM. Thanks very much for doing that test. The results are quite interesting! I'll try to see why this happens, though I'm not exactly sure at a first glance. Again, thanks very much. – Water May 22 '15 at 15:57
0

Think the problem is that you are using toArray() without parameters that returns Object[]. Take a look at

public <T> T[] toArray(T[] a)
Stan
  • 1,410
  • 10
  • 14