In working on the next release of LingPipe, I’m finding many opportunities to generalize the generic types. Let’s take the cached feature parser I’m working on right now in the other window. Feature vectors are extracted as instances of
Map<String, ? extends Number>
I had to do that to allow LingPipe utility classes like
util.ObjectToCounterMap<String> extends Map<String, Counter>
util.ObjectToDoubleMap<String> extends Map<String, Double>
to be usable as collectors for feature extractors.
The cached feature parser accepts a map that’ll act as a cache for feature vectors. The most general type that’d work is:
Map<? super E, ? extends Map<String, ? extends Number>>
Intuitively (?!), this says that we need any old map (it can be a subtype for the outer type) that can hold mappings from
E objects to results that implement maps from strings to numbers.
But is anyone other than an obsessive type geek going to be able to even parse the expression? Should I just go with the simpler form here:
Map<E, Map<String, ? extends Number>>
I think it makes sense to use the simpler form in this setting because it’s going to be a cache that gets constructed under the user’s control and most of the constructors for these kinds of maps are very general (e.g. all of Java’s
Map implementations and LingPipe’s
You might think making the nested types covariant would help, but as Josh Bloch points out in Effective Java (2nd Ed.), this is problematic because the direction of subclassing differs for arguments and return results. That’s why there are both
extends in the wildcard capture definitions above. In the simplest case, you extend return results and super arguments.