scphantm
scphantm

Reputation: 4563

Downcasting Guava map classes

I have about a month of work into a system that is using a guava datatype of

ImmutableMap<String, Object>

Things work great, even my developers love it. Its awesome. Until yesterday. We have a recordset that we have to bring in as an ImmutableListMultimap and we have to cast it to our ImmutableMap type.

here is our compile error

ImmutableListMultimap<String, Object> tst = new bla bla bla;
        ImmutableMap<String, Object> tst2 = (ImmutableMap<String, Object>) tst.asMap();

The error itself is

Cannot cast from ImmutableMap<String,Collection<Object>> to ImmutableMap<String,Object>

Now doing this cast isn't an option. It must be done or we have to go back and reengineer a month of work. Not going to happen. The only option I have right now to solve this is to build a factory class that iterates the tst variable, recasting the values to basic objects and dumping that into tst2. But, this tst var could contain thousands of entries which makes that solution unwise.

Another option is Apache Collections Transforms, but since those are just iterators as well, I don't gain much.

Is there anything more elegant that I can do to downcast a generic or has Sun's brilliant concept of generics type erasure just doomed me.

Upvotes: 0

Views: 219

Answers (3)

Louis Wasserman
Louis Wasserman

Reputation: 198321

So, one approach that will not generate any compile warnings would be, instead of a cast, to use ImmutableMap.copyOf. This will not actually perform a copy, as it can detect that would be redundant, but it will take an ImmutableMap<? extends K, ? extends V> and return an ImmutableMap<K, V>.

It will still be a constant time operation, and a cast under the surface, but it will not generate warnings.

Upvotes: 0

Andy Turner
Andy Turner

Reputation: 140494

You can use the ImmutableMap.copyOf method, with explicit constraints:

ImmutableMap<String, Object> tst2 = ImmutableMap.<String, Object>copyOf(tst.asMap());

Note that this doesn't necessarily copy the map - under some (many) circumstances, tst == tst2 (they are identical references).

If you absolutely must avoid making a copy of the map, you would need to use a wildcard type, since ImmutableMap<K, Object> and ImmutableMap<K, Collection<Object>> are not related types:

ImmutableMap<String, ?> tst2 = tst.asMap();

The fact that the value is a wildcard type shouldn't affect your ability to do anything with the map values as Objects:

for (Map.Entry<String, ?> entry : tst2.entrySet()) {
  Object value = entry.getValue();
  //...
}

Upvotes: 3

maaartinus
maaartinus

Reputation: 46482

It can't be cast for a good reason, but there's simple workaround:

ImmutableMap<String, Object> tst2 =
    (ImmutableMap<String, Object>) (Object) tst.asMap();

This looks possibly a bit saner:

    ImmutableListMultimap<String, Object> tst = ...
    ImmutableMap<String, ? extends Collection<Object>> asMap = tst.asMap();
    @SuppressWarnings("unchecked")
    ImmutableMap<String, Object> tst2 = (ImmutableMap<String, Object>) asMap;

The generic system is not perfect (if it was, it'd even more complicated) and sometimes it's hard to understand. The unchecked cast above is safe because of the immutability: There's no way to put in an Object (this would go against the Map<String, Collection<Object>> signature).

Upvotes: 2

Related Questions