37

I am trying to compile the following code:

private String dataToString(){
    Map data = (HashMap<MyClass.Key, String>) getData();
    String toString = "";
    for( MyClass.Key key: data.keySet() ){
        toString += key.toString() + ": " + data.get( key );
    return toString;
}

I get an error in the for line that says:

incompatible types
found : java.lang.Object
required: MyClass.Key

The getData() method returns an Object (but in this case the Object returned has the HashMap structure). MyClass.Key is an enum that I have created for the purposes of my application (in another class file - MyClass).

When I created a foreach loop with the same structure in MyClass.java, I did not encounter this problem.

What am I doing wrong?

troyal
  • 2,499
  • 6
  • 25
  • 28
  • There no need to cast getData() to a HashMap when you're just going to assign it to a Map. Rather cast it a Map. What if getData() returns a non-HashMap (like a TreeMap)? – Steve Kuo Jan 15 '09 at 19:57
  • I actually left out some information here...getData() is actually getData(String key), where key specifies the desired Object I wish to get. So since I know the Object I am getting, I know exactly what I should cast it to. – troyal Jan 15 '09 at 20:00

5 Answers5

43

A slightly more efficient way to do this:

  Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData(); 
  StringBuffer sb = new StringBuffer();
  for (Map.Entry<MyClass.Key,String> entry : data.entrySet()) {
       sb.append(entry.getKey());
       sb.append(": ");
       sb.append(entry.getValue());
   }
   return sb.toString();

If at all possible, define "getData" so you don't need the cast.

Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • Upvoted this since .entrySet() is the most efficient way of iterating through Map and it holds original references to the Map so if you alter the Entry you alter the actual map and so on. It's also very convenient. – Esko Jan 15 '09 at 19:50
  • I would, but getData() is used all over this project as well as others, so it's best to leave it as is (returning an Object). – troyal Jan 15 '09 at 19:53
  • +1, you should definitely change getData() to return Map. It will not break older code. – Craig P. Motlin Jan 15 '09 at 20:06
  • awesome example! thanks. i needed to use entry.getKey() and entry.getValue(). – ufk Jan 24 '10 at 12:45
  • @ufk - you're right. That's what I get for writing code off the top of my head instead of looking at the API docs. When you've used as many languages as I have, you can't always keep every detail of the API in memory. – Paul Tomblin Jan 24 '10 at 12:56
38

Change:

Map data = (HashMap<MyClass.Key, String>) getData();

to

Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData();

The problem is that data.keySet() returns a Collection<Object> if data is just a Map. Once you make it generic, keySet() will return a Collection<MyClass.Key>. Even better... iterate over the entrySet(), which will be a Collection<MyClass.Key, String>. It avoids the extra hash lookups.

Craig P. Motlin
  • 26,452
  • 17
  • 99
  • 126
5

You could grab the entrySet instead, to avoid needing the key class:

private String dataToString(){    
    Map data = (HashMap<MyClass.Key, String>) getData();    
    String toString = "";    
    for( Map.Entry entry: data.entrySet() ) {        
        toString += entry.getKey() + ": " + entry.getValue();
    }    
    return toString;
}
Richard Campbell
  • 3,591
  • 23
  • 18
5

I found this simple example at java forum. Its syntax is very similar to the List's foreach, which was what I was looking for.

import java.util.Map.Entry;
HashMap nameAndAges = new HashMap<String, Integer>();
for (Entry<String, Integer> entry : nameAndAges.entrySet()) {
        System.out.println("Name : " + entry.getKey() + " age " + entry.getValue());
}

[EDIT:] I tested it and it works perfectly.

dialex
  • 2,706
  • 8
  • 44
  • 74
3

Motlin's answer is correct.

I have two notes...

  1. Don't use toString += ..., but use StringBuilder instead and append data to it.

  2. Cast which Martin suggested will give you unchecked warning, which you won't be able to get rid of, because it is really unsafe.

Another way, without warning (and with StringBuilder):

private String dataToString(){
    Map<?, ?> data = (Map<?, ?>) getData();
    StringBuilder toString = new StringBuilder();
    for (Object key: data.keySet()) {
        toString.append(key.toString());
        toString.append(": ");
        toString.append(data.get(key));
    }
    return toString.toString();
}

This works, because toString method which you call on key is defined in Object class, so you don't need casting at all.

Using entrySet is even better way, as it doesn't need to do another look-up in map.

Sumit Singh
  • 15,743
  • 6
  • 59
  • 89
Peter Štibraný
  • 32,463
  • 16
  • 90
  • 116
  • You can get rid of the warning with @SuppressWarning("unchecked") – Ryan Ahearn Jan 15 '09 at 19:42
  • That's just hiding problems, not really fixing them. @SuppressWarning("unchecked") should be used with GREAT caution. – Peter Štibraný Jan 15 '09 at 19:43
  • I am wondering if it is better to store data.keSet() in a local variable or is the foreach loop optimized? – Alfred Mar 04 '10 at 01:48
  • @Alfred: you don't need to store data.keySet() into your own variable. for (Object key: data.keySet()) is funcionally equivalent to: Iterator i = data.keySet().iterator(); while (i.hasNext()) { Object key = i.next(); ... rest of for loop goes here ... } That is, data.keySet() is evaluated only once. – Peter Štibraný Mar 04 '10 at 08:10