5

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

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 2
    A raw type erases all generics used in that type. This is by design. I don't think there's any way around the unchecked cast. – Slaw Jan 09 '21 at 12:29
  • I might be missing something, but why can't you just cast the stream itself if you know what type of thing it has? `((Stream)getStream())` – Sweeper Jan 09 '21 at 12:31
  • @Sweeper Isn’t that what I am implicitly doing in my workaround? It still looks like a workaround to me. Am I too picky? – Ole V.V. Jan 09 '21 at 12:34
  • Ah yes, but is there actually an unchecked warning? My IDE didn't issue one... (maybe I configured it not to?) – Sweeper Jan 09 '21 at 12:36
  • @Sweeper My work Eclipse and home Eclipse did differently on that point, so yes, probably a preferences question. – Ole V.V. Jan 09 '21 at 12:38
  • 1
    @Sweeper Compiling directly with `javac` outputs `Note: Main.java uses unchecked or unsafe operations.` for me. – Slaw Jan 09 '21 at 12:41
  • 5
    Cast `Stream` to `Stream>`. That's the only way to get rid of the raw type without an *unchecked* operation. After that, `map` can be used as intended. – Holger Jan 10 '21 at 13:17
  • Hey, @Holger, that sounds indeed like the way I want to go. Feel free to post as an answer if you have got the time. Thanks very much. – Ole V.V. Jan 10 '21 at 20:54
  • This question has expected behaviour, a specific problem (pasted error message), minimal reproducible example, search and research (workaround found). Just so that I may continue improving (my question track record does not look that good): What does it take for a question not to be downvoted? (I skimmed [this](https://meta.stackexchange.com/questions/228358/why-are-my-questions-on-stack-overflow-getting-downvotes-without-explanation) and [this](https://meta.stackoverflow.com/questions/252677/when-is-it-justifiable-to-downvote-a-question) without getting closer to an understanding.) – Ole V.V. Feb 26 '21 at 08:19

1 Answers1

7

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.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161