Issue creating ImmutableMap with Class<?> as

2020-08-18 06:10发布

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

1条回答
我命由我不由天
2楼-- · 2020-08-18 06:20

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 parameter K for both Date and Integer. One would think that this should fail as we are passing inconsistent method arguments, means we are passing different types for the same type parameter K. But surprisingly it doesn't.

Class<Date> and Class<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:

K := Class<? extends Object&Serializable&Comparable<?>>

That is the return value of the method would really be:

ImmutableMap<Class<? extends Object&Serializable&Comparable<?>>, String>

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:

ImmutableMap<Class<?>, String> map = ImmutableMap.<Class<?>, String>of( 
    Integer.class, "Integer", 
    Date.class, "Date" 
);

This will now work, as compiler knows from the explicit type argument that the return value would be of type - ImmutableMap<Class<?>, String>

Oddly enough it does work if I add a cast to Class<?> to any(!) of the keys

As soon as you type cast any of the element to Class<?>, since Class<?> denotes family all all the instances of Class<T>, and hence it is the common supertype for all the Class instances. So, the type argument will be inferred as Class<?> automatically. And it would work fine.

查看更多
登录 后发表回答