javajava-streamtype-parametergeneric-collectionsraw-types

Collecting a raw stream into a typed collection?


I am calling a library method that returns a raw Stream. I know the type of the elements in the stream and want to collect into a collection with the declared element type. What is the nice, or the not so appalling way of doing it?

Minimal reproducible example

For the sake of a reproducible example suppose I want to call this method:

@SuppressWarnings("rawtypes")
static Stream getStream() {
    return Stream.of("Example");
}

I had hoped that this would work, and I have not understood why it doesn’t:

    List<String> stringList = getStream().map(s -> (String) s).collect(Collectors.toList());

I get Type mismatch: cannot convert from Object to List. Why?

Workaround

An unchecked cast works. To narrow down the bit that needs to be marked as deliberately unchecked, we may do

    @SuppressWarnings("unchecked")
    Stream<String> stringStream = getStream();
    List<String> stringList = stringStream.collect(Collectors.toList());

On both Java 8 and Java 11 the returned list is a java.util.ArrayList, and it contains the expected String element.

What my search turned up or not

I tried my search engine searching for terms like java collect raw stream. It didn’t lead me to anything helpful. There’s a closed Java bug (link at the bottom) mentioning that once you operate on raw types, everything gets raw. But this fully raw version doesn’t work either:

    List stringList = getStream().collect(Collectors.toList());

I am still getting Type mismatch: cannot convert from Object to List. How come?

We are coding for Java 8 (a little while still).

I know this question has something opinion-based about it. I am still hoping that you can provide me with some facts that will help me write code that will generally be considered nicer.

Background: Hibernate 5

The method I want to call is org.hibernate.query.Query.getResultStream() on a raw Query (a Query lacking type parameter). I want to get the stream because I have special requirements for the type of collection I am collecting into. In my production code I am using Collectors.toCollection() (not Collectors.toLlist() as in the example in this question). BTW while writing this question I found a way to persuade Hibernate to return a typed stream. I still found the question interesting enough to post.

Link


Solution

  • The hint is in the Java Bug record alright: Once I use a raw type, everything gets raw. Stream.collect() is a generic method too, so its raw version returns — Object, don’t be surprised.

    The Stream method I am calling is declared like this:

        <R,A> R collect​(Collector<? super T,A,R> collector)
    

    So it returns an R, and what R is it can gather from the passed collector (be it Collectors.toList() or some Collectors.toCollection()). Once everything has gone raw, the deduction no longer works. And collect() returns Object.

    To conclude: Generics are good. When I cannot avoid getting something raw, convert it to its generic counterpart as early as possible.

    EDIT: With thanks to Sweeper and Holger for valuable input in comments, this is my preferred solution:

        Stream<?> wildcardStream = getStream();
        List<String> stringList = wildcardStream.map(s -> (String) s)
                .collect(Collectors.toList());
        System.out.println(stringList.getClass());
        System.out.println(stringList);
    

    Output on Java 11:

    class java.util.ArrayList
    [Example]
    

    Casting Stream to Stream<?> (either implicitly or explicitly) gets rid of the raw type without an unchecked operation. After that map() can be used as intended. And there is no need for @SuppressWarnings("unchecked").

    This also means that a solution for my original Hibernate scenario is to cast the Query object to a Query<?> before calling getResultStream() rather than only casting the stream. And then cast each individual object in a map() in the stream operation in the same way as above.