Home » Java » Java Generic issue: cannot cast Map<K,V> to M extends Map<K, V>-Exceptionshub

Java Generic issue: cannot cast Map<K,V> to M extends Map<K, V>-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

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) -> ...);
How to&Answers:

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:

  • remove M from collectToHashMapWithRandomMerge as well, or:
  • make collectToHashMapWithRandomMerge accept a Supplier<M>, so that its caller can decide the type of map.

Answer:

M extends Map<K, V> means a specific class that extends Map. HashMap is a class that extends Map, but M could be, say, LinkedHashMap; a HashMap isn’t a LinkedHashMap.

You appear already to be given a Supplier<M>, as a parameter:

public static <E, K, V, M extends Map<K, V>>
Collector<E, ?, M> collectToHashMapWithRandomMerge(Supplier<M> mapSupplier /* HERE */, BiConsumer<M, E> accumulator) {
    return Collector.of(mapSupplier(),
            accumulator,
            TjiCollectionUtils.randomMapMerger());
}

So, just use mapSupplier (the parameter) instead of mapSupplier() (the result of invoking the method).