views:

19904

answers:

4

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?

+18  A: 

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
Thanks! Can you explain why that fixes it?
Blue
Because the way you declared it, Map data, declares data as a Map of unknown type, so keySet() returns Object. Making the change tells the compiler that the keys are MyClass.Key, not Object.
Paul Tomblin
When you assign getData to just Map data you're actually assigning it to Map<Object, Object> by explicitly defining it as Map<MyClass.Key, String> data you allow the foreach loop to retain knowledge of what type the key is.
Ryan Ahearn
-1 entrySet() is much better than this.
cletus
@cletus, true, but that was not a problem in this question.
Peter Štibraný
I don't think cletus read my answer. Obviously you could/should use both.
Craig P. Motlin
+3  A: 

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 lookup in map.

Peter Štibraný
You can get rid of the warning with @SuppressWarning("unchecked")
Ryan Ahearn
That's just hiding problems, not really fixing them. @SuppressWarning("unchecked") should be used with GREAT caution.
Peter Štibraný
I am wondering if it is better to store data.keSet() in a local variable or is the foreach loop optimized?
Alfred
@Alfred: you don't need to store data.keySet() into your own variable. for (Object key: data.keySet()) is funcionally equivalent to:Iterator<Object> 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ý
Okay thanks for your answer :)
Alfred
+4  A: 

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
Iterating through the entrySet is more efficient than going through the keySet, even though I need to output the names of the keys as well?
Blue
The entrySet is more efficient because you don't have to do a lookup on every key.
Michael Myers
@Blue - way more efficient BECAUSE you're using both key and value.
Paul Tomblin
+16  A: 

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
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
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).
Blue
+1, you should definitely change getData() to return Map<MyClass.Key, String>. It will not break older code.
Craig P. Motlin
+1 entrySet() is better than keySet() plus key lookups.
cletus
awesome example! thanks. i needed to use entry.getKey() and entry.getValue().
ufk
@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