0
public class Dog {

    String name;

    public Dog(String name) {
        this.name = name;
    }

    public boolean equals(Object o) {
        if ((this == o) && ((Dog) o).name == name) {
            return true;
        } else {
            return false;
        }
    }

    public int hashCode() {
        return name.length();
    }
}

class Cat {
}

enum Pets {

    DOG, CAT, HORSE
}

class MapTest {

    public static void main(String[] args) {
        Map<Object, Object> m = new HashMap<Object, Object>();
        m.put("k1", new Dog("aiko"));
        m.put("k2", Pets.DOG);
        m.put(Pets.CAT, "CAT Key");
        Dog d1 = new Dog("Clover");
        m.put(d1, "Dog key");
        m.put(new Cat(), "Cat key");
        System.out.println(m.get("k1"));
        String k2 = "k2";
        System.out.println(m.get(k2));
        System.out.println(m.get(Pets.CAT));
        System.out.println(m.get(d1));
        System.out.println(m.get(new Cat()));

        // UNABLE TO UNDERSTAND BELOW PART OF CODE
        d1.name = "magnolia";
        System.out.println(m.get(d1)); // #1 //prints Dog Key
        d1.name = "clover";
        System.out.println(m.get(new Dog("clover"))); // #2 // prints null
        d1.name = "arthur";
        System.out.println(m.get(new Dog("clover"))); // #3 // prints null 
    }
}

I am trying to understand the hashcode() implementation from kathy and berts book for SCJP6. While I have basic understanding of hashcode implementation, a scenario from the book tricked me up and I am confused with the output.

Based on the program above, it says:

  • In the first call to get(), the hashcode is 8 (magnolia) and it should be 6 (clover), so the retrieval fails at step 1 and we get null.
  • In the second call to get(), the hashcodes are both 6, so step 1 succeeds. Once in the correct bucket (the "length of name = 6" bucket), the equals() method is invoked, and since Dog's equals() method compares names, equals() succeeds, and the output is Dog key.
  • In the third invocation of get(), the hashcode test succeeds, but the equals() test fails because arthur is NOT equal to clover.

The above three line talks about length of the name and its relevant hashcode() implementation but how is it comparing the length when get() method is called?

kittu
  • 6,662
  • 21
  • 91
  • 185
  • Is the hashcode-function used anywhere? I just see the definition. – hamena314 May 05 '15 at 17:51
  • Out put of map keys are printed based on `Hashcode()` method implemented Ex: `System.out.println(m.get(d1));` – kittu May 05 '15 at 17:53
  • Your `//prints XXX` comments do not match the explanation. The explanation makes sense. Your comments do not. – lmz May 05 '15 at 17:53
  • `m.get(d1)` is a function of the HashMap, it has nothing to do with your hashcode() function. What you are getting are the values to certain keys. – hamena314 May 05 '15 at 17:56
  • @lmz Exactly, even I am wondering it is printing wrong values but the comments are from the book(kathy&bert) and I implemented the same program out of it. – kittu May 05 '15 at 17:57
  • @kittu and what is the actual output on your system? Does it match the explanation ("null", "Dog key", "null")? – lmz May 05 '15 at 17:58
  • 1
    @hamena314 looks like you are still new to java ;) – kittu May 05 '15 at 17:59
  • @lmz No it doesn't match – kittu May 05 '15 at 18:00
  • 1
    BTW You should use `.equals()` to compare Strings. If you use `==` it checks that it is exactly the same object. – Peter Lawrey May 05 '15 at 18:19
  • @Peter Lawrey You are right I didn't realise that tx – kittu May 05 '15 at 18:23
  • @kittu String literals with the same contents will be the same object, but this won't work for String you create e.g. with `.toString()` – Peter Lawrey May 05 '15 at 18:24
  • @kittu: Actually ... I am. Seems like I've learned something now. :D – hamena314 May 05 '15 at 18:25
  • @PeterLawrey "but this won't work for String you create e.g. with .toString()" ? Didn't get it – kittu May 05 '15 at 18:27
  • 1
    @kittu e.g. `String s = new StringBuilder().append("a").append("b").toString();` so `s == "ab"` is false as it is a different object which happens to have the same characters, `s.equals("ab")` is true as it compares the contents of the String. – Peter Lawrey May 05 '15 at 18:30

2 Answers2

2

The equals implementation does not match what the text says. It will never be true for different objects! Also hashcode should have a capital C hashCode. This code works as described:

import java.util.*;
class MapTest {

static class Dog {

    String name;

    public Dog(String name) {
        this.name = name;
    }

    public boolean equals(Object o) {
        if ((this == o)) {
            return true;
        } else if (((Dog)o).name.equals(this.name)) {
            return true;
        } else {
            return false;
        }
    }

    public int hashCode() {
        return name.length();
    }
}

static class Cat {
}

static enum Pets {

    DOG, CAT, HORSE
}
    public static void main(String[] args) {
        Map<Object, Object> m = new HashMap<Object, Object>();
        m.put("k1", new Dog("aiko"));
        m.put("k2", Pets.DOG);
        m.put(Pets.CAT, "CAT Key");
        Dog d1 = new Dog("Clover");
        m.put(d1, "Dog key");
        m.put(new Cat(), "Cat key");
        System.out.println(m.get("k1"));
        String k2 = "k2";
        System.out.println(m.get(k2));
        System.out.println(m.get(Pets.CAT));
        System.out.println(m.get(d1));
        System.out.println(m.get(new Cat()));
        // UNABLE TO UNDERSTAND BELOW PART OF CODE
        d1.name = "magnolia";
        System.out.println(m.get(d1)); // #1 
        d1.name = "clover";
        System.out.println(m.get(new Dog("clover"))); // #2
        d1.name = "arthur";
        System.out.println(m.get(new Dog("clover"))); // #3
    }
}

EDIT: and to answer "how is it comparing the length when get() method is called?":

put uses hashCode() to calculate a bucket where the value (and the key) will be placed.

get() will look for a bucket first. The bucket is determined by the hashCode() which is the length of the name. If the length of the name is not the same, the bucket will (hopefully) be different so get() will not find the matching entry. This explains #1: same object, but the hash code changed between put and get so get looks in a different bucket.

Now, once the appropriate bucket is found, get() will then look through the keys in the bucket and compare them using equals() since more than one key can be put in the same bucket. This explains #3: same bucket but objects not equal(). #2 is same bucket and objects are equal() since the names are equal().

lmz
  • 1,560
  • 1
  • 9
  • 19
  • Wow! Thanks a lot man. Just because of 'C' it is not working. That was a good catch – kittu May 05 '15 at 18:17
  • 1
    also the equals implementation did not work for different objects so m.get(new Dog(...)) would never work. – lmz May 05 '15 at 18:18
  • 1
    Please use Java's [`@Override` annotation](http://stackoverflow.com/questions/94361/when-do-you-use-javas-override-annotation-and-why) each time you're overriding methods from the parent class. This way the compiler will save you from simple typos like `hashcode`. – Mick Mnemonic May 05 '15 at 18:29
  • @Mick Mnemonic Already did that when I found out the mistake ;) – kittu May 05 '15 at 18:36
  • @kittu does this answer your question now? :) – lmz May 05 '15 at 18:43
  • 1
    And that's why the third statement `d1.name = "arthur";` `System.out.println(m.get(new Dog("clover")));` is printing null because the equals method is checking for the value (value==value) of key? even though the length is 6? – kittu May 05 '15 at 18:58
  • @kittu yes. That looks in the same bucket but the keys are not `equal()` – lmz May 05 '15 at 19:10
  • @kittu: So ... the method `hashcode()` never gets called like I've said and I was right for the wrong reasons? xD – hamena314 May 06 '15 at 06:33
  • @hamena314 "I was right for the wrong reasons?" Didn't understand that. or may be you were wrong for the right reason :p. – kittu May 06 '15 at 06:40
1

Hashcode value is correct which is equals to 8. shouldnt be 6 as you suggest. You're changing the name value pointed by d1 when making d1.name = "magnolia";

At step1, its null because of the hashcode function name and it can't find a "magnolia" hashed index in the hashmap.)

Important: Map works with hashcode and equals. hashcode is used to find the object you're looking for in your hashmap. When this object is found, equals is called to ensure that they are indeed the same (value) as what you're expecting. Your hashcode should be unique. Think about what if you have to different names with the same lenght. Also, be sure that the function names are hashCode, equals and override them.

I don't why your hashcode is lenght based but I suggest that you use an HashCodeBuilder() and EqualsBuilder().

bigjel
  • 57
  • 8
  • when I say `System.out.println(m.get(d1));` d1 in this case is a key right? and I can see the key `d1` as in `m.put(d1, "Dog key");` so the output should be Dog key right? but how come it is null just because of `d1.name="magnolia"`? – kittu May 05 '15 at 18:22
  • yes, d1 is the indeed the key. so the lenght you're expecting is 8 instead of 6. I updated the answer hope its more clear :) – bigjel May 05 '15 at 18:42
  • One last doubt. The last statement length is 6 as in `d1.name = "arthur"; System.out.println(m.get(new Dog("clover")));` but it prints null even though length is same. So it is checking the length as well as the value of the key? – kittu May 05 '15 at 18:51
  • 1
    Here is the process: When you do get new Dog("clover") it creates a new object Dog and then look for his hashcode which will be 6, so hashcode = 6. Then it goes in the hashmap at index = hashcode = 6. If there is an object, it retreives it then calls equals function to be sure that this the same. if equals fails, then it returns null. if nothing was at index 6, it returns null also. better? – bigjel May 05 '15 at 18:55
  • thats why its better to be sure that the hashcode is unique. in your case, that means that you cant have two differents names with the same lenght (their hascode will be the same) in the hashmap which in my opinion is not what you want – bigjel May 05 '15 at 18:59
  • A lot better! now I get the clarity. Thanks a lot – kittu May 05 '15 at 19:01
  • 1
    an easy way to deal with hascode is to use HashCodeBuilder(). You can append as many items you want and it is code clean. @Override public int hashCode() { return new HashCodeBuilder() .append(name) .toHashCode(); } – bigjel May 05 '15 at 19:04
  • 100 likes if it facebook ;) – kittu May 05 '15 at 19:05