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 returnCollector<T,?,Long>
. Under the hood, it makes use ofsummingLong
, which in turn returnsCollector<T,?,Long>
although it internally utilizeslong[]
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 anObject
.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()