While trying to write some Generic code I faced an issue. Of course, I found some workarounds, but still, why the code below is not working?
private static <K, U, M extends Map<K, U>>
Supplier<M> mapSupplier() {
return HashMap::new;
}
This returns
Error:(25, 17) java: incompatible types: bad return type in lambda expression
no instance(s) of type variable(s) K,V exist so that java.util.HashMap<K,V> conforms to M
Update: This map supplier is needed for me to create custom map collector:
public static <E, K, V, M extends Map<K, V>>
Collector<E, ?, M> collectToHashMapWithRandomMerge(BiConsumer<M, E> accumulator) {
return Collector.of(mapSupplier(),
accumulator,
TjiCollectionUtils.randomMapMerger());
}
Calling Collector.of with HashMap::new also causes the same compilation error
Ideally, I don't want to create additional method params and just use the following:
public static <E, K, V, M extends Map<K, V>>
Collector<E, ?, M> collectToHashMapWithRandomMerge(BiConsumer<M, E> accumulator) {
return Collector.of(HashMap::new,
accumulator,
TjiCollectionUtils.randomMapMerger());
}
Answer I ended up making:
public static <E, K, V>
Collector<E, ?, Map<K, V>> collectToMapWithRandomMerge(BiConsumer<Map<K, V>, E> accumulator) {
return Collector.of(HashMap::new,
accumulator,
TjiCollectionUtils.randomMapMerger());
}
And it's called in the following way:
MyCollectionUtils.collectToMapWithRandomMerge(
(Map<String,Integer> m, SomeClassToExtractFrom e) -> ...);
This seems to be a common misconception of generics, which is that the callee decides what types the generic parameters are. In fact, the caller does.
Whoever calls mapSupplier
gets to decide what K
, U
and M
are. Let's say that I am calling it, and I want K
to be Integer
, U
to be String
, and M
to be Hashtable<Integer, String>
. This is valid because Hashtable
implements Map
.
Supplier<Hashtable<Integer, String>> htSupplier = mapSupplier();
Hashtable<Integer, String> ht = htSupplier.get();
As the caller, I would expect the above to work, but htSupplier.get
, with your implementation, would actually give me a HashMap<Integer, String>
, which is an unrelated type to Hashtable
(in terms of inheritance hierarchy).
In other words, mapSupplier
has single-handedly decided that M
should be HashMap<K, U>
while also saying that it will work on any M
that implements Map<K, U>
.
Whenever you see yourself writing a "generic" method that decides what its generic parameters are, that method probably shouldn't have that generic parameter. Therefore, mapSupplier
should be probably be rewritten without the M
parameter:
private static <K, U>
Supplier<HashMap<K, U>> mapSupplier() {
return HashMap::new;
}
EDIT:
Seeing the caller, I think you can either:
M
from collectToHashMapWithRandomMerge
as well, or:collectToHashMapWithRandomMerge
accept a Supplier<M>
, so that its caller can decide the type of map.