1

I have the following situation. I have a HashMap in Java with keys as strings. Then in some stage , in runtime I create strings equal to those keys in order to retrieve the data from that map.The strings are created as follows within "for" loop:

 String keyToRetrive = "lights[" + Integer.toString(i) + "]" + ".Intensity";

The strange thing about it that when I iterate through the map to find the key that equals that string ,even when the match is found the search steps over.So in this search loop :

  while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry) iter.next();
        if (name == entry.getKey()) {  ///name- "lights[0].Intesity"
            uniformOut = (ICleanable) entry.getValue();
            break;
        }
    }

The key with the name "lights[0].Intesity" never returns true even that the map contains one.How I solved it .I used hashCode() for both compared string values.So this version does work:

 while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry) iter.next();
        if (name.hashCode() == entry.getKey().hashCode()) {
            uniformOut = (ICleanable) entry.getValue();
            break;
        }
    }

UPDATE: After being pointed to the fact that "==" doesn't work good and "equals()" should be used I would like to narrow the question:Why "==" does work for strings which were not created from several concatenated blocks? I mean, if I defines key string to compare agains as a simple single string:

 String foo="foo";

Such a string is compared using "==" against HashMap key all right.

I am not an expert Java programmer so can anybody explain why it works this way?

Michael IV
  • 11,016
  • 12
  • 92
  • 223
  • 1
    re: your update, java puts strings that are compile time constants into a pool so that there is only one actual string with that value and all references to it are pointers to the same pooled string and pass reference equality. This is not true for strings that are assembled at runtime. – Affe Jun 26 '12 at 07:04
  • 1
    BTW why do you loop through your map and dont use 'map.get(name)'? Should work much faster. As to the other question: Java does some memory optimizations, so that every time you specify a String literal 'String foo = "foo";' Java will put that object into a cache and reuse it everytime the same literal is used. So 'String foo2 = "foo";' will be the same object as foo, therefore '==' will work just fine. But basically you should never use '==' on anything but primitives or for checking against null, saves a lot of headache. – pushy Jun 26 '12 at 07:06

3 Answers3

8

You are comparing Strings using == operator. Use equals() instead:

name.equals(entry.getKey())

This is a common pitfall in Java, see How do I compare strings in Java? and Difference between Equals/equals and == operator?.


BTW (unrelated to your problem) when concatenating strings you don't need to call toString() explicitly so this:

"lights[" + Integer.toString(i) + "]" + ".Intensity"

can be replaced with:

"lights[" + i + "]" + ".Intensity"

It'll work for i of any type, not only int.

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • Hmm , I thought "equals" would not evaluate the string.But in such a case why for non run time concatenated strings "==" does work>? – Michael IV Jun 26 '12 at 06:40
  • @MichaelIV Don't be confused by the comment about concatenation, it's unrelated. – mergeconflict Jun 26 '12 at 06:48
  • 2
    correct Thomasz. My 2 cens : iterating through a map to fetch a value is a bad practice, the aim of a map is to get value by key. Uses map.get(key) instead. – jocelyn Jun 26 '12 at 06:53
5

When you compare objects using ==, you're performing a "referential equality" comparison, meaning that you're checking whether the two references point at the same String object in memory. If you're familiar with C, it would be like:

char* a = some_string();
char* b = some_other_string();
if (a == b) { ... }

On the other hand, when you compare objects using .equals(), you're performing a "structural equality" comparison, meaning that you're checking whether the two objects contain equivalent data. Again, the C analog of this would be:

char* a = some_string();
char* b = some_other_string();
if (strcmp(a, b) == 0) { ... }

Now, the thing you really, really don't want to do is to compare the two objects' hash codes. Why not? Because two objects with the same hash code are not necessarily equal! They might be, but you can't correctly rely on it.


Update: You also asked about why == works for string literals. The answer is because the Java compiler doesn't allocate constant strings on the heap; instead it stores them in the constant pool of the class in which they're used. So, if you write:

String foo1 = "foo";
String foo2 = "foo";

Then the compiler will have both references point at the same location in the class's constant pool. If, however, you write:

String foobar1 = "foobar";
String foobar2 = "foo" + bar();
String bar() { return "bar"; }

The compiler isn't quite smart enough to figure out that foobar2 is logically equivalent to foobar1. However, even if you know that the two variables are compile-time constants, you still should keep it simple and use .equals().

mergeconflict
  • 8,156
  • 34
  • 63
  • +1 for a great [tag:c] analogy (that actually explains the root cause of the problem) – Tomasz Nurkiewicz Jun 26 '12 at 07:00
  • Thanks a lot for this answer ! That is what I suspected was the case! But can you clarify why then it does work ok with a simple string.It doesn't references the same string object ,yet the comparison passes . – Michael IV Jun 26 '12 at 07:01
  • Great answer again! Now I understand the logic behind this behavior.Thanks again. – Michael IV Jun 26 '12 at 07:07
  • Sure thing! This is a very common point of confusion for new Java developers. Of course, now that you know all this, you should go back and just use `Map.get()` as other commenters suggested ;) – mergeconflict Jun 26 '12 at 07:09
1

The others have stated why your code won't work, however:

1) If you're using a HashMap, you should use map.get(key) to retreive the value, not an interator of entries; that's the whole point of the Hash Map.

2) Use generics, avoid explicitly casting as much as you can!

WhyNotHugo
  • 9,423
  • 6
  • 62
  • 70