I am trying to create an ImmutableMap
that maps classes to strings (note: this is, of course, just an example!). However, something like
ImmutableMap<Class<?>, String> map = ImmutableMap.of(
Integer.class, "Integer",
Date.class, "Date"
);
gives me the following error
Type mismatch: cannot convert from ImmutableMap<Class<? extends Object&Comparable<?>&Serializable>,String> to ImmutableMap<Class<?>,String>
Oddly enough it does work if I add a cast to Class<?>
to any(!) of the keys, i.e.
ImmutableMap<Class<?>, String> map = ImmutableMap.of(
Integer.class, "Integer",
Date.class, "Date",
(Class<?>) String.class, "String",
long.class, "Long"
);
will work just fine. I'm sort of puzzled by this behavior: For one, why does it not work without casts? All of these are classes and it really doesn't get any more generic than Class<?>
, so why does it not work? Secondly, why does a cast on any one of the keys get it to work?
(side note: if you're wondering why I even want to do such a thing – yes, it's because of Reflection…)
Edit: I actually just figured out that this will work, but I'd stil like to understand the above behavior
ImmutableMap<Class<?>, String> map = ImmutableMap.<Class<?>, String>builder()
.put( Integer.class, "Integer" )
.put( Date.class, "Date" )
.build();
This is how the compiler infers type parameters when you pass inconsistent method arguments. If you notice, the
ImmutableMap.of(K, V, K, V)
method uses the same type parameterK
for bothDate
andInteger
. One would think that this should fail as we are passing inconsistent method arguments, means we are passing different types for the same type parameterK
. But surprisingly it doesn't.Class<Date>
andClass<Integer>
are capture convertible to all of the following:Class<? extends Object>
Class<? extends Serializable>
Class<? extends Comparable<?>>
So, the type
K
is inferred as a mixture of all:That is the return value of the method would really be:
Of course, you can't assign it directly to
ImmutableMap<Class<?>, String>
, as they are incompatible type. Also note that, you can't declare your map explicitly as above, because you can't give multiple bounds for wildcards. It's just how the compiler infers the type.For these kinds of situation, where compiler cannot correctly infer the type arguments as you required, you can pass explicit type arguments, while method invocation, which is what you did in your last try:
This will now work, as compiler knows from the explicit type argument that the return value would be of type -
ImmutableMap<Class<?>, String>
As soon as you type cast any of the element to
Class<?>
, sinceClass<?>
denotes family all all the instances ofClass<T>
, and hence it is the common supertype for all theClass
instances. So, the type argument will be inferred asClass<?>
automatically. And it would work fine.