Home > Mobile >  Generic type mismatch while using a Collector returned by the method - Java 17
Generic type mismatch while using a Collector returned by the method - Java 17

Time:04-24

I was experimenting with records and streams.

I've created these records to count the number of letters in a text.

record Letter(int code) {
    Letter(int code) {
        this.code = Character.toLowerCase(code);
    }
}

record LetterCount(long count) implements Comparable<LetterCount> {
    @Override
    public int compareTo(LetterCount other) {
        return Long.compare(this.count, other.count);
    }

    static Collector<Letter, Object, LetterCount> countingLetters() {
        return Collectors.collectingAndThen(
            Collectors.<Letter>counting(), 
            LetterCount::new);
    }
}

And here is the snippet where they're used:

final var countedChars = text.chars()
    .mapToObj(Letter::new)
    .collect(
        groupingBy(Function.identity(), 
            LetterCount.countingLetters()                      // compilation error
            // collectingAndThen(counting(), LetterCount::new) // this line doesn't produce error
        ));

The snippet shown above doesn't produce error if I comment out collectingAndThen() to be used as the downstream collector within the groupingBy().

However, the compiler gets lost when I'm trying to use LetterCount.countingLetters() as the downstream collector.

I'm getting the following error message:

Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
        Collector cannot be resolved to a type
        Type mismatch: cannot convert from Collector<Letter,capture#17-of ?,LetterCount> to Collector<Letter,Object,LetterCount>
        The method countingLetters() from the type LetterCount refers to the missing type Collector
        The method entrySet() is undefined for the type Object

CodePudding user response:

Interface Collector has the following declaration:

public interface Collector<T,​A,​R>

Where the second generic type parameter A denotes the type of the mutable container which is used internally to accumulate the results of the reduction. This type is usually hidden by the implementations.

Your method countingLetters() declares the return type as follows:

Collector<Letter, Object, LetterCount>

And that implies the type of the mutable container of the collector returned by this method is expected to an Object.

Remainder: generics are invariant, i.e. if you've said Object you have to provide only Object (not it's subtype, not unknown type ?, only the Object type itself).

And that is incorrect for several reasons:

  • All collectors that are built-in in JDK are hiding their types of mutable containers. Collector conting that you are using in your code declares to return Collector<T,?,Long>. Under the hood, it makes use of summingLong, which in turn returns Collector<T,?,Long> although it internally utilizes long[] as a container. That is because it doesn't make sense to expose these implementation details. And as a consequence, generic parameters of the return type you've declared doesn't conform to the generic parameters of the collector that you are returning. I.e. since you are given an unknown type ?, you can't declare to return an Object.

  • Even if you wouldn't use built-in collectors, but your own custom collector instead, it still will not be a very bright idea to expose the actual type of its container. Simply because sometime in the future, you might want to change.

  • Object class is immutable, hence it's fruitless to use it as container type (if you try to implement a custom collector) because it is not capable to accumulate data.


The bottom line: the second generic parameter in the collector returned by the countingLetters() method isn't correct.

To fix it, you have to change the type of the mutable container to be either:

  • an unknown type ?;
  • or to an upper bounded wildcard ? extends Object.
public static Collector<Letter, ?, LetterCount> countingLetters()
  • Related