Skip to content
Advertisement

Java Stream Collect() classifier can’t detect type

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.

8 People found this is helpful
Advertisement