26

How can I cast a Map<Object,Object> to Map<String,String> in the cleanest way?

Is there a way to do that without iterating over the map?

Thanks

Mark
  • 67,098
  • 47
  • 117
  • 162
  • 3
    Why is a `Map` declared as `Map` in first place? – BalusC Oct 16 '11 at 23:54
  • 1
    It's just an abstract example, it could be Map to be casted to Map – Mark Oct 16 '11 at 23:56
  • 2
    If the latter is your concrete requirement, then you really have to loop over it. But if it was *actually* a `Map` which is incorrectly been declared as `Map`, then you could just have casted on `(Map)` (without generic type arguments). – BalusC Oct 16 '11 at 23:58
  • You seem to be confused between casting, which is just telling the compiler something that is already true, and conversion, which is operating on the data. – user207421 Oct 17 '11 at 01:27
  • There are some cases where a map of objects is appropriate, but it is the exception not the rule. – Jay Mar 26 '14 at 00:06

5 Answers5

13

The actual answer is:

Map<Object,Object> valueMap = ...;
@SuppressWarnings("unchecked")
Map<String,String> targetMap = (Map)valueMap;
Jay
  • 19,649
  • 38
  • 121
  • 184
Chris J
  • 9,164
  • 7
  • 40
  • 39
12

I think it's a good idea to explain why the simple solution doesn't work and why you never, ever should use this.

Assume you could cast List<Object> to List<String> (the same applies to Map, just a simpler interface). What would you expect to happen from the following code:

List<Object> m = Something;
m.add("Looks good.");
m.add(42);
List<String> s = (List<String>)m; // uhuh, no we don't want that.
String myString = s.get(1); // huh exception here.

Now you CAN hack it indeed using Bohemians/Chris solution, but you basically destroy Java's type system. DON'T DO THAT. You don't want a List<String> to contain an Integer! Have fun debugging that later on - the additional code of looping through all variables will avoid lots of headaches and hardly is a performance problem.

If there's a reason to declare the Map as taking an Object instead of a String someone may add any object to it - usually you should be able to avoid this with a better generic.

Voo
  • 29,040
  • 11
  • 82
  • 156
  • 1
    More real life example. See `java.util.Properties extends Hashtabl` but you want to use and `Map` instead of using `getProperty` method of Properties. So you have to convert first. – takacsot Oct 31 '12 at 14:49
  • And I really like your answering style ... reminds me on how I approach many questions ;-) – GhostCat Apr 01 '19 at 17:45
  • I hate when tool decides whether I should do something or not. What if I need to cast `List` to `List`, I can't redesign my code, it's two 3rd party libraries, so I should map one list to another one just to do it in java'sh way? – deathangel908 Apr 09 '19 at 13:33
  • @deathangel No you should do it that way to avoid breaking the type system. – Voo Apr 09 '19 at 14:38
  • What do I do i map case? `stringList.stream().map(e -> (Object) e).collect(toList())`? What exception will I get If I just cast `List` to `List`? Every rule has an exception. – deathangel908 Apr 09 '19 at 14:44
  • @death The given example can be trivially adapted to produce the same runtime exception if you cast `List` to `List` (hint: what can you add to a `List`? What do you expect a `List` to contain?) – Voo Apr 09 '19 at 22:41
  • Alternatively, this is a good reason to only use immutable data structures! – Chad Jul 15 '20 at 19:22
  • Never? Often you don't control the API returning the map (or List). I'm using GAE memcache which returns Map) from getAll(). I put the objects in there so I KNOW they are . Sometimes you need to cast to avoid an unnecessary copy of a large map. Isn't that the point of casting? Why does the compiler complain in this case? – paul Sep 12 '21 at 00:27
9

If you want something clean, you must convert "deeply"! keys and values.

Using Java 8 stream, this take only a few lines:

static public Map<String, String> toStringString(Map<? extends Object, ? extends Object> map) {
        return map
                .entrySet()
                .stream()
                .collect(Collectors.toMap(
                    e -> e.getKey().toString(),
                    e -> e.getValue().toString()
                ));
    }
Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
0
 public  Map<String, String> castOnlyStringValues(final Map<String, Object> map) {
    return map.entrySet().stream()
              .filter(x -> String.class.isAssignableFrom(x.getValue().getClass()))
              .map(x -> (Entry<?, ?>) x).map(x -> (Entry<String, String>) x)
              .collect(toMap(Entry::getKey, Entry::getValue));
}
Nelson Azevedo
  • 311
  • 3
  • 4
-1

Building on Chris J's answer, here's his "hack" code in action, showing it works:

public static void main(String[] args) {
    Map<Object, Object> valueMap = new HashMap<Object, Object>();
    valueMap.put("foo", "bar");
    @SuppressWarnings("unchecked")
    Map<String, String> targetMap = (Map<String, String>) ((Object) valueMap); // hack alert!
    for (Map.Entry<String, String> entry : targetMap.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        System.out.println(key + "=" + value); // prints foo=bar :)
    }
}

Wow! No runtime exceptions :)

Community
  • 1
  • 1
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    Horrible, horrible hack. Anyone using that in production code should be taken out and shot in my opinion ;) Note that I assume you only mentioned that, because it's a neat trick and not as an advice to use it. Always fun though to show people how a `Map` can contain an integer.. – Voo Oct 17 '11 at 00:48
  • Well... I did say it was a dirty hack ;-) – Chris J Oct 17 '11 at 21:30
  • While I agree that this is usually this is wrong, there're cases where a map of objects is appropriate. – Jay Mar 26 '14 at 00:06