2

I am running into this weird issue where I am iterating over a list of responses. when I try to get the answer from each response by the question, most of them get the answer correctly except one where getting the answer from the hashmap gives null. I have ran the debug mode in eclipse, and compared the question that I try to get its value from the hashmap getAnswerMap() with the one inside that hashmap and both seem to be exactly the same, but I still get null.

for (SurveyResponse response : responses) {
      MultipleChoiceAnswer answer = (MultipleChoiceAnswer) response.getAnswerMap().get(question);
        ....
        ....
      }

Then, I thought it is a hashcode issue, so I added another ugly line of code to check hashcodes, and they actually have the same hashcode and the additional following line worked and did set answer correctly.

for (SurveyResponse response : responses) {
      MultipleChoiceAnswer answer = (MultipleChoiceAnswer) response.getAnswerMap().get(question);
      for (Entry entry: response.getAnswerMap().entrySet()) {
        if (entry.getKey().hashCode() == question.hashCode()) answer = (MultipleChoiceAnswer) entry.getValue();
        ....
        ....
      }

However, this is very ugly and I would really like to get the answer correctly from the hashmap. Any suggestions?

UPDATE: calling both hashCode() and equals() method on both objects shows that both have equal hashcodes and equals() returns true. I suspect that as one of the answers down indicate, the problem might be that the question was inserted with a different hashcode when it was inserted in the hashmap. Therefore, calling the get method in question returns null because the object I am trying to get does not have the same hashcode as the old one. Extremely helpful answers guys!

Sam
  • 2,398
  • 4
  • 25
  • 37

4 Answers4

3

One thing to watch out for: Make sure the class you're using as a key is immutable -- otherwise, a key will hash to one thing when you put it in, but something different when you take it out.

Edit: It doesn't have to be immutable, but it has to be true that it can only be changed in a way that doesn't change the hashcode. Making the entire object immutable is the simplest way to do that, but it's not the only way.

Tyler
  • 21,762
  • 11
  • 61
  • 90
  • I don't know why this got a downvote, for all we know, it might be the solution to the problem. – biziclop Jul 06 '11 at 23:05
  • 1
    Yes and no - you're right that the hash code must remain the same, but mutating an object does not necessarily have to change its hash code, so long as the object is *logically* the same object as it was before. Take for example a `User` class - say each user has a unique ID number, that makes for a perfect hash code. But a `User`'s ID number (i.e. hash code) doesn't have to change just because they change their password, for example. (For the record, I didn't downvote). – Mac Jul 06 '11 at 23:06
  • @Mac True, but it still is a good idea to declare your hash code forming fields final. It helps keeping your code maintainable. – biziclop Jul 06 '11 at 23:11
  • Actually, now that I think about it, and since my hashcode, and equals methods are very consistent, I believe that this might be the issue. Very good point! – Sam Jul 07 '11 at 16:17
2

One more glass ball guess:

You have an equals method like this one:

class Question {

    // ...


    public boolean equals(Question q) {
       // do intelligent comparison
    }

    public int hashCode() {
        // calculate hash code
    }

}

But here you don't really override the equals(Object) method from Object, but simply declare a new one beside this. The HashMap does not know anything about your new method, it will simply call the original one for comparing your key object in the map with the query key (after finding one with matching hashCode).

Declare the method like this, instead:

    @Override
    public boolean equals(Object o) {
        if(! (o instanceof Question))
           return false;
        Question q = (Question)o;
        // do intelligent comparison
    }

(The @Override annotation lets the compiler check that you are really overriding a method here, not just creating a new one.)

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • 2
    Better yet, use the `@Override` annotation, and then the compiler will go "Hey, you're not actually overriding the method that you think you're overriding!" – Tyler Jul 06 '11 at 23:37
  • 1
    The IDE support in Intellij, and I think in Eclipse also, is very good at helping out with the boilerplate. And it doesn't make this kind of hair-pulling mistake. – Ed Staub Jul 06 '11 at 23:41
1

To make an object an 100% deterministic key with a HashMap you need to override hashCode() and equals() where they are consistent in that equals() always returns true when the hashCode()s are the same.

Here is an old article from Brian Goetz on IBM developerWorks, but the contents are still applicable today:

Why override equals() and hashCode()?

What would happen if Integer did not override equals() and hashCode()? Nothing, if we never used an Integer as a key in a HashMap or other hash-based collection. However, if we were to use such an Integer object for a key in a HashMap, we would not be able to reliably retrieve the associated value, unless we used the exact same Integer instance in the get() call as we did in the put() call. This would require ensuring that we only use a single instance of the Integer object corresponding to a particular integer value throughout our program. Needless to say, this approach would be inconvenient and error prone.

The interface contract for Object requires that if two objects are equal according to equals(), then they must have the same hashCode() value. Why does our root object class need hashCode(), when its discriminating ability is entirely subsumed by that of equals()? The hashCode() method exists purely for efficiency. The Java platform architects anticipated the importance of hash-based collection classes -- such as Hashtable, HashMap, and HashSet -- in typical Java applications, and comparing against many objects with equals() can be computationally expensive. Having every Java object support hashCode() allows for efficient storage and retrieval using hash-based collections.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • Yeah I totally agree with what you say. However, I checked both equals() and hashCode() for question and entry.getKey() and both are consistent. – Sam Jul 06 '11 at 23:01
  • But is the hashCode the same as at the time the object was put in the map! – Affe Jul 06 '11 at 23:16
  • "where they are consistent in that equals() always returns true when the hashCode()s are the same" - actually - just as your article states - that's not true. Two different objects may have the same hashcode and still return false for equals and this would be a perfectly valid implementation. Hashcollisions are quite possible – Voo Jul 07 '11 at 01:37
0

It's likely that you haven't overridden equals(..) correctly - it is a requirement for a HashMap to work correctly

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • I just checked that by a system.out.println, and entry.getKey().equals(question) returned true. – Sam Jul 06 '11 at 22:59