Java generics: put() on Map<String,capture#3-of ? extends AbstractClass> is not applicable for the arguments (String, AbstractClass)

capture, generics, java

Solution

According to the javadoc for `Object.getClass()`, the returned type is a wildcard based compile-time type of the expression. Since the compiler only knows that `this` returns an AbstractClass instance, `this.getClass()` returns `Class<? extends AbstractClass>`.

This means your call to `getNameMap` in the constructor will return a `Map<String, ? extends AbstractClass>`. Which means that, while the returned Map has values of a specific (non-wildcard) type, that exact type isn't known at compile-time; the compiler only knows the Map's values are required to be either AbstractClass or something that inherits from AbstractClass. So the compiler can't safely add `this` as a value, since it isn't known at compile-time which subtype of AbstractClass `this` represents.

To use a simpler example: if a method returned `Map<String, ? extends Number>` then the compiler wouldn't know whether it was safe to add an Integer to the Map, because the Map's actual, non-wildcard type might be `Map<String, Double>`, `Map<String, Short>`, etc.

As for a solution: I don't think there is a way to have a Map use generics to match each individual key's type with its corresponding value's type. I would forget about using bounded types on the inner Maps' values, and use dynamic casting instead:

private static Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> map = new HashMap<>();

private static synchronized Map<Class<? extends AbstractClass>, Map<String, AbstractClass>> getNameMap(Class<T> clazz) {
    // same as before
}

public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) {
    return clazz.cast(getNameMap(clazz).get(name));
}

Problem

I'm trying to implement a sort of intern factory for multiple classes that extend from a common parent. Much of the logic is identical, but it can't really be inherited because the lookups need to be static. The desired syntax is something like: ``` Car c = AbstractClass.valueOf(Car.class, "Ford"); ``` with Car having specific methods related to cars, but the instances are stored in a common cache. Here's what I have so far. My compile error is on the put in the constructor: "The method put(String, capture#3-of ? extends AbstractClass) in the type Map is not applicable for the arguments (String, AbstractClass)" ``` import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; public abstract class AbstractClass { private static Map<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>> map = new HashMap<Class<? extends AbstractClass>, LinkedHashMap<String, ? extends AbstractClass>>(); private static synchronized <T extends AbstractClass> Map<String, T> getNameMap(Class<T> clazz) { LinkedHashMap<String, T> nameToEnum = (LinkedHashMap<String, T>) map.get(clazz); if (nameToEnum == null) { nameToEnum = new LinkedHashMap<String, T>(); map.put(clazz, nameToEnum); } return nameToEnum; } public static <T extends AbstractClass> T valueOf(Class<T> clazz, String name) { return getNameMap(clazz).get(name); } public static <T extends AbstractClass> Collection<T> VALUES(Class<T> clazz) { return getNameMap(clazz).values(); } public static <T extends AbstractClass> Set<T> SORTED_VALUES(Class<T> clazz) { return new TreeSet<T>(getNameMap(clazz).values()); } AbstractClass(String name) { AbstractClass.getNameMap(this.getClass()).put(name, this); } } ```

Original source