0

I am struggling with the reason why I am getting the second line of outputs as "Line2: null" when running the following piece of code on HashMap:

import java.util.*;

class Dog {
  public Dog(String n) {name = n;}
  public String name;
  public boolean equals(Object o) {
    if((o instanceof Dog) && (((Dog)o).name == name)) {
        return true;
    }
    else {return false;}
  }
  public int hashCode() {return name.length();}
}

public class HelloWorld{

 public static void main(String []args){
    Map<Object, Object> m = new HashMap<Object, Object>();
    Dog d1 = new Dog("clover");
    m.put(d1, "Dog key");
    System.out.println("Line1: " + m.get(d1));
    d1.name = "magnolia";
    System.out.println("Line2: " + m.get(d1));
    d1.name = "clover";
    System.out.println("Line3: " + m.get(new Dog("clover")));
    d1.name = "arthur";
    System.out.println("Line4: " + m.get(new Dog("clover")));
 }
}

The outputs displayed are:

Line1: Dog key

Line2: null

Line3: Dog key

Line4: null

Yes, I do realize that modifying the instance variable name, in turn, affects the hashcode of the instance of Dog because of the way I calculate the hashcode. But, I am using the same instance as the key! So, why cannot the get() method find the corresponding value? It seems like once a pair is pushed into a HashMap, the key is hardcoded with the value forever! Is this how it is supposed to work, meaning that, once a hashcode has been determined for a value before placing the pair in HashMap, the hashcode can never be modified again?

softwarelover
  • 949
  • 8
  • 20
  • 2
    Yes, of course. If you try to get() with a modified hashcode, then how is the HashMap supposed to know what to match against? – Oliver Charlesworth Jun 20 '15 at 15:55
  • You should compare Strings (e.g. `Dog.name`) using `equals` instead of `==`. Also, you're expecting lots of collisions with your current `hashCode` implementation. You could use `return name.hashCode()` instead. – Mick Mnemonic Jun 20 '15 at 15:55
  • Oliver is correct.. Your overridden `hashcode` method returns value based on Dog's name field. As `clover` returns 6 and `magnolia` returns 8, when you call get(), hashcode of the object passed as key is used, as the previous entry's hashcode is 6 which is different from what you passed the second time (8), it will not be able to find the corresponding entry and returns null – Arkantos Jun 20 '15 at 16:11

1 Answers1

0

Yes, I do realize that modifying the instance variable name, in turn, affects the hashcode of the instance of Dog because of the way I calculate the hashcode. But, I am using the same instance as the key! So, why cannot the get() method find the corresponding value?

This explanation is going to be a bit oversimplified, but it should still illustrate what's going on here. Think of a HashMap as an array of key-value pairs. The hashCode value is used to decide at which index to get/put a given value.

For example, if your hash code returns 7, then it will try to get/put the value at index 7 in the array. So, let's say you're doing a put operation, but index 7 was already full. There are a few of ways to deal with this, but the simplest is just to have a bucket (e.g. a linked list) of values with the same hash at each array index, and you just add your new value to the bucket.

Now let's say you're doing a get operation. You check the index in the array corresponding to your hash value—but you're not guaranteed that's the value you're looking for (due to possible hash collisions). You need to make sure that the key at that location is also equal to the key used for the lookup. If the keys aren't equal, then you keep looking (in the bucket). If there's nowhere left to look (i.e. you've searched the whole bucket), then the value isn't in the map.

That's the part where your code is breaking. You're looking in the right bucket (since the hash for your key is the same as the original hash), but the equals method now returns false when you do the "deep" comparison to check that you actually have the right key/value pair.

Is this how it is supposed to work, meaning that, once a hashcode has been determined for a value before placing the pair in HashMap, the hashcode can never be modified again?

When you understand how you'd implement a HashMap using an array (as described above), it should become obvious that this is in fact the expected behavior, and that mutating the key is a very bad idea.


Some side notes...

You should use equals to compare the names too:

((Dog)o).name.equals(name))

Here is what you currently have:

((Dog)o).name == name)

That checks if the name string is the same instance rather than if the strings have the same value.

You could simplify your equals method quite a bit by just returning the result of your logical operations:

public boolean equals(Object o) {
  return (o instanceof Dog) && (((Dog)o).name.equals(name));
}
DaoWen
  • 31,184
  • 6
  • 65
  • 95
  • 1
    Yeah but since they are using String literals, it's not related to the behavior the question is about. – Radiodef Jun 20 '15 at 15:56
  • @Radiodef - ah, good point. I just saw `==` for comparing strings and jumped to a false conclusion. – DaoWen Jun 20 '15 at 15:57
  • Thanks. I will review the bucket concept that I read before. And about ==, it is luckily working here only because I am using String literals. Most definitely, the proper way would be to replace == with equals(). – softwarelover Jun 20 '15 at 16:17