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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With