Advertisement
If you have a new account but are having problems posting or verifying your account, please email us on hello@boards.ie for help. Thanks :)
Hello all! Please ensure that you are posting a new thread or question in the appropriate forum. The Feedback forum is overwhelmed with questions that are having to be moved elsewhere. If you need help to verify your account contact hello@boards.ie

Java: HashMap question

Options
  • 25-05-2009 11:10pm
    #1
    Registered Users Posts: 260 ✭✭


    OK, I'm having a bit of trouble understanding one basic concept. Let me apologise in advance for the lengthy post: I want to present as complete a picture as possible!

    I've got a wee program here (part of my SCJP course) that is adding a bunch of objects to a HashMap. What I'm interested in here is Dog objects.

    All a Dog has is a name (public String name). Dog has overridden the equals() method, such that two Dogs are equal if they have the same name. It has overridden the hashCode() method, such that a hashcode is equal to the length of the Dog's name:
    public int hashCode(){
       return name.length(); // base hashcode solely on length of name
    }
    

    So I have:
    Map<Object, Object> m = new HashMap<Object, Object>();
    
    Dog d1 = new Dog("Clover"); //setting the name to "Clover"
    m.put(d1, "Dog Key"); //key is based on d1's name, I believe, so the key = 6
    

    Retreiving this key/value pair is straightforward, with
    m.get(d1);
    

    Now, because the name is public, I can change it directly (I am assuming this is made public so you don't have to go faffing about with getName() and setName(), and so I, the student, can concentrate on the new stuff, the Map stuff).

    So I am then instructed to change my Dog's name to "Magnolia", which is fine, and then attempt to retrieve it from the HashMap:
    d1.name = "Magnolia"; //name is public, so no need for a setName() method
    System.out.print(m.get(d1)); //m is still my HashMap
    

    This fails. That's fine. So it should: I've got a key/value pair of [6 letters]/"Dog Key" in my HashMap, but there are no [8-letters] keys in there at all.

    My question is this, then (finally!): is it the case that the program finds no [8-letters] keys in the HashMap and hence returns 'null'?

    If it did find a "bucket" of 8-letter keys, would it then check all the objects there to see if any of them was equal to the object I had given it, and return only those objects which were (in this case, all dogs with name "Magnolia")?

    Please let me know if any clarification is required. Thank you.


Comments

  • Registered Users Posts: 3,945 ✭✭✭Anima


    To be honest, I haven't used "hashCode()" ever and I may be misinterpreting this but I have used HashMaps a good deal. In your code

    [PHP]m.get(d1);[/PHP]

    you are trying to access the data directly without using the key, which is not how a HashMap works. It's like a safe which needs a specific key, or password, to access the data after you put it in. If you did

    [PHP]m.get("Dog Key");[/PHP]

    it should return the object you "put" in initially. Other wise you're using it like an ArrayList() or something similar. Also, having a HashMap accept "<Object,Object>" is not a good idea.


  • Registered Users Posts: 6,240 ✭✭✭hussey


    Anima wrote: »
    [PHP]m.get(d1);[/PHP]

    you are trying to access the data directly without using the key, which is not how a HashMap works. It's like a safe which needs a specific key, or password, to access the data after you put it in. If you did

    [PHP]m.get("Dog Key");[/PHP]

    it should return the object you "put" in initially

    No it shouldn't
    he used :
    m.put(d1, "Dog Key");

    d1 = Key
    "Dog Key" = value

    so by supplying d1 he should get "Dog Key" back


  • Registered Users Posts: 3,945 ✭✭✭Anima


    Oh yeah sorry about that, was a bit drunk reading that last night lawl.


  • Registered Users Posts: 981 ✭✭✭fasty


    hussey wrote: »
    No it shouldn't
    he used :
    m.put(d1, "Dog Key");

    d1 = Key
    "Dog Key" = value

    so by supplying d1 he should get "Dog Key" back

    Not if he changes d1's name member he won't.


  • Registered Users Posts: 5,618 ✭✭✭Civilian_Target


    OK, it's like this.

    If you have an object with no overwritten equals or hashcode, then you will always find the object you want, assuming you remain in the same location in the heap. If your object is serialized at any point (eg. it's an EJB in a pool), you may never find it in the map again. Maybe a GC Compact will move it. But short term for simple programs at least, this method is reliable.

    If you think this is a problem, you should overwrite the equals and hashcode method.
    Taking your example, you could go
    public boolean equals(Object o){
    if(o instanceof Dog){
       if(this.getName() == null && o.getName() == null){ return true;}
       else return this.getName().equals(o.getName);
    }
    return false;
    
    public int hashCode(){
    if(this.getName() == null) return 0;
    else return this.getName().hashCode()
    }
    

    Now, it doesn't matter if we serialize, unserialize, garbage collect, whatever. The hashcode will remain the same as long as the name remains the same. But if you change the name, then the hashcode changes. Thus, when you go
    hashmap.contains(dog)
    
    you probably won't find it.
    Why?
    Because the original position of the dog will be based on an index, made using the hashcode. If you change the name, the hashmap will look for the dog at the new address in the index, and it won't find it because the dog hasn't been moved. If you iterate through the map though, you will find it, because it's still in there, and it doesn't use the index to make the iterator.

    So how do you avoid this? Easy. You can define equals on what you want, like the name. But you should only define hashCode on final properties of your objects.

    Remember this contract:
    If x.equals(y), then the hashCode of x and y must be the same.
    If !x.equals(y), then the hashCode can be the same, but should aim to differentiate any x from y in as many cases as possible.

    Personally, if I was writing a heart monitor in Java, I would write
    public int hashCode(){ return 0;}
    


  • Advertisement
  • Registered Users Posts: 260 ✭✭pdebarra


    Thanks for the replies, folks.

    My question remains, though:

    In my example above, according to the SCJP course book, the program doesn't reach the correct "bucket" (the area of 6-letter keys) in the HashMap, so it doesn't even call the equals() method to check whether any objects in the area that it has reached match the object I'm looking for.

    I take this to mean that, since there is no "bucket" with 8-letter keys (it was asked to find such an area, because I gave it as an index a Dog with an 8-letter name), it just returns null, and moves on.

    If there were such an area, and it contained objects, I am assuming that my program would check each one to see (a) if it's a Dog and (b) if it is a Dog, whether its name is "Magnolia".

    The simplicity of this example means, of course, that it might pick out a completely different Dog from the one I was looking for, because all it looks at is the Dog's name.

    Are my assumptions correct?


  • Registered Users Posts: 5,618 ✭✭✭Civilian_Target


    Like I said, if the hashCode gives back the same value, you will find it. If it doesn't, you might not.
    So, in your example you might get the value, or you might get null.


  • Registered Users Posts: 260 ✭✭pdebarra


    So my understanding was correct.

    Thank you.


Advertisement