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

11,648

Solution 1

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));
}

Solution 2

If you just want to store anything that is an AbstractClass, just declare your map as

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

This would allow you to store any instance of AbstractClass or its subclasses in the inner map, against AbstractClass or one of its sub class.

Share:
11,648
user1889540
Author by

user1889540

Updated on June 18, 2022

Comments

  • user1889540
    user1889540 almost 2 years

    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);
        }
    
    }