I have the following code reading lines from a text file:
try (BufferedReader br = new BufferedReader(new InputStreamReader(Uio.decodeFrom(url)))) { return br.lines() .parallel() .map(s -> s.split("\s+")) // split by whitespace .collect( Collectors.groupingByConcurrent( arr -> arr[0], // String 1 Collectors.groupingByConcurrent( arr -> arr[arr.length-1], // String 2 Collectors.counting() ) ) ); } catch (IOException e) { throw new UncheckedIOException(e); }
The text file has data like
String1 ... cols ... String2 string1data ... otherdata ... string2data ...
And I’m trying to group by String1
and String2
and get their counts. Then end result should be a Map<String, Map<String, Long>>
. However, with the code above, the compiler is saying that the collect()
returns a ConcurrentMap <Object, ConcurrentMap<Object, Long>>
.
Why are the keys not Strings?
Advertisement
Answer
I can duplicate this error message, but the replacement of String
with Object
in the error message appears to be a red herring. The real problem is that Java’s generics are invariant.
If the call to collect
is returning a ConcurrentMap<String, ConcurrentMap<String, Long>>
, that doesn’t match Map<String, Map<String, Long>>
, even though a ConcurrentMap
is a Map
. The inner Map
type must match exactly without a wildcard and bounds.
If you introduce an upper-bounded wildcard to the return type, it will compile without error. Have it return the type Map<String, ? extends Map<String, Long>>
, so that the inner ConcurrentMap<String, Long>
will match.
A return type of Map<String, ConcurrentMap<String, Long>>
will also work.
It’s unclear why String
wasn’t captured until the generics invariant issue was worked out. Just a guess: the compiler didn’t capture String
yet, because it found the invariant generics issue first. Once the invariant generics issue is resolved, it compiles without error, implying String
does get inferred.