Java generics: put() on Map<String,capture#3-of ? extends AbstractClass> is not applicable for the arguments (String, AbstractClass)
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.
user1889540
Updated on June 18, 2022Comments
-
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); } }