Hot questions for Operating ImmutableMap in Guava

Top 10 Java Open Source / Guava / Operating ImmutableMap

initializing a Guava ImmutableMap

Question: Guava offers a nice shortcut for initializing a map. However I get the following compiler error (Eclipse Indigo) when my map initializes to nine entries.

The method "of(K, V, K, V, K, V, K, V, K, V)" in the type ImmutableMap is not applicable for the arguments "(String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String, String)"

ImmutableMap<String,String> myMap = ImmutableMap.of(
        "key1", "value1", 
        "key2", "value2", 
        "key3", "value3", 
        "key4", "value4", 
        "key5", "value5", 
        "key6", "value6", 
        "key7", "value7", 
        "key8", "value8", 
        "key9", "value9"
        );

The message appears to say that

An ImmutableMap has a maximum size of four pairs of key,value.

Obviously, this cannot be the case but I can't figure out what to do to increase the size of my initializer.

Can someone tell me what is missing?

Answer: Notice that your error message only contains five K, V pairs, 10 arguments total. This is by design; the ImmutableMap class provides six different of() methods, accepting between zero and five key-value pairings. There is not an of(...) overload accepting a varags parameter because K and V can be different types.

You want an ImmutableMap.Builder:

ImmutableMap<String,String> myMap = ImmutableMap.<String, String>builder()
    .put("key1", "value1") 
    .put("key2", "value2") 
    .put("key3", "value3") 
    .put("key4", "value4") 
    .put("key5", "value5") 
    .put("key6", "value6") 
    .put("key7", "value7") 
    .put("key8", "value8") 
    .put("key9", "value9")
    .build();

Google Collections ImmutableMap iteration order

Question: I need combination of Google Collection ImmutableMap and LinkedHashMap — immutable map with defined iteration order. 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.

However there are no more details. Quick test shows that this might be true, but I want to make sure.

My question is: can I rely on iteration order of ImmutableMap? If I do ImmutableMap.copyOf(linkedHashMap), will it have same iteration order as original linked hash map? What about immutable maps created by builder?

Answer: To be more precise, the ImmutableMap factory methods and builder return instances that follow the iteration order of the inputs provided when the map in constructed. However, an ImmutableSortedMap, which is a subclass of ImmutableMap sorts the keys.


Java - Are there any Stream Collectors that return ImmutableMap?

Question: I find myself wanting a variant of Collectors.toMap which returns an ImmutableMap, such that I can do:

ImmutableMap result = list.stream().collect(MyCollectors.toImmutableMap(
    tuple -> tuple._1(), tuple -> tuple._2());

ImmutableMap is not strictly required but seems the best choice as I require: lookup by key, and retaining original iteration order. Immutability is always preferred too.

Note that FluentIterable.toMap(Function) is not sufficient because I need both a key-mapping function as well as a value-mapping function.

Answer: You don't need to write an anonymous class for this collector. You can use Collector.of instead:

public static <T, K, V> Collector<T, ?, ImmutableMap<K,V>> toImmutableMap(
            Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper) {
    return Collector.of(
               ImmutableMap.Builder<K, V>::new,
               (b, e) -> b.put(keyMapper.apply(e), valueMapper.apply(e)),
               (b1, b2) -> b1.putAll(b2.build()),
               ImmutableMap.Builder::build);
}

r if you don't mind collecting the results into a mutable map first and then copy the data into an immutable map, you can use the built-in toMap collector combined with collectingAndThen:

ImmutableMap<String, String> result = 
     list.stream()
         .collect(collectingAndThen(
             toMap(
                 tuple -> tuple._1(), 
                 tuple -> tuple._2()),
             ImmutableMap::copyOf));

Issue creating ImmutableMap with Class<?> as key

Question: 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?

Answer: 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:

  1. Class<? extends Object>
  2. Class<? extends Serializable>
  3. 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.

So in your case, try this code:

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