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?
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?
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.
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.
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.
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.