Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue creating ImmutableMap with Class<?> as key

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();
like image 487
Ingo Bürk Avatar asked Oct 02 '13 18:10

Ingo Bürk


People also ask

How do you instantiate an immutable Map?

Using Java 9 Factory Of() method In Java, use of() with Set, Map or List to create an Immutable Map.

Is immutableMap ordered?

It seems that ImmutableMap itself actually has defined iteration order, at least its documentation says: An immutable, hash-based Map with reliable user-specified iteration order.

How can we create immutable HashMap?

We used to use the unmodifiableMap() method of Collections class to create unmodifiable(immutable) Map. Map<String,String> map = new HashMap<String, String>(); Map<String,String> immutableMap = Collections. unmodifiableMap(map); Lets test this in JShell.

How can we convert mutable Map to immutable in Java?

You can simply create a new HashMap from the existing Map using the copy constructor. HashMap<String, Object> = new HashMap<>(immutableMap); Note that this is a brand new object.


1 Answers

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.

like image 139
Rohit Jain Avatar answered Oct 02 '22 08:10

Rohit Jain