-1

I was reading Effective Java Item 9 and decided to run the example code by myself. But it works slightly different depending on how I insert a new object that I don't understand what exactly is going on inside. The PhoneNumber class looks:

public class PhoneNumber {

private final short areaCode;
private final short prefix;
private final short lineNumber;

public PhoneNumber(int areaCode, int prefix, int lineNumber) {
    this.areaCode = (short)areaCode;
    this.prefix = (short) prefix;
    this.lineNumber = (short)lineNumber;
}

@Override public boolean equals(Object o) {
    if(o == this) return true;
    if(!(o instanceof PhoneNumber)) return false;
    PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode;
}
}

Then according to the book and as is when I tried,

    public static void main(String[] args) {

         HashMap<PhoneNumber, String> phoneBook = new HashMap<PhoneNumber, String>();
         phoneBook.put(new PhoneNumber(707,867,5309), "Jenny");
         System.out.println(phoneBook.get(new PhoneNumber(707,867,5309)));
    }

This prints "null" and it's explained in the book because HashMap has an optimization that caches the hash code associated with each entry and doesn't check for object equality if the hash codes don't match. It makes sense to me. But when I do this:

    public static void main(String[] args) {

         PhoneNumber p1 = new PhoneNumber(707,867,5309);
         phoneBook.put(p1, "Jenny");
         System.out.println(phoneBook.get(new PhoneNumber(707,867,5309)));
    }

Now it returns "Jenny". Can you explain why it didn't fail in the second case?

pandagrammer
  • 841
  • 2
  • 12
  • 24
  • Must be the same instance somehow, unless you are actually overriding `int hashCode()` somewhere. – Elliott Frisch Jul 21 '14 at 05:57
  • Nope I am not overriding hashCode anywhere. That was the whole point of this example that if you don't override hashCode, it won't work properly. I understand that it returns "Jenny" because those two are the same instances somehow, but would like to know what the difference came from compared to the first example. – pandagrammer Jul 21 '14 at 05:59
  • 4
    That can't be the code you're running - you're missing semi-colons at the ends of the statements, for a start. That makes me wonder what *else* is different about the actual code you're running. We can't help you understand code we can't see. (Just adding the semi-colons, I can't reproduce your problem.) – Jon Skeet Jul 21 '14 at 06:03
  • @JonSkeet Sorry, just added the semi-colons and the static main method. That's exactly the code I ran. – pandagrammer Jul 21 '14 at 06:08
  • 1
    @pandagrammer: I very much doubt it, as now you haven't even declared `phoneBook`. Please show a short but *complete* program demonstrating the problem. – Jon Skeet Jul 21 '14 at 06:21
  • An unrelated remark: despite the name a phone number is not a number but a string of digits. It makes a difference if there are leading zeros. So your class would better store it as a `String` rather than `short` components. – Henry Jul 21 '14 at 06:22
  • @pandagrammer I downvoted your question. Your third code sample doesn't even compile - phoneBook is not declared. As JonSkeet said - we can only help you if you post actual working code. – jcsahnwaldt Reinstate Monica Mar 18 '15 at 19:58

4 Answers4

1

The experienced behaviour might depend on the Java version and vendor that was used to run the application, because since the general contract of Object.hashcode() is violated, the result is implementation dependent.

A possible explanation (taking one possible implementation of HashMap):

The HashMap class in its internal implementation puts objects (keys) in different buckets based on their hashcode. When you query an element or you check if a key is contained in the map, first the proper bucket is looked for based on the hashcode of the queried key. Inside the bucket objects are checked in a sequencial way, and inside a bucket only the equals() method is used to compare elements.

So if you do not override Object.hashcode() it will be indeterministic if 2 different objects produce default hashcodes which may or may not determine the same bucket. If by any chance they "point" to the same bucket, you will still be able to find the key if the equals() method says they are equal. If by any chance they "point" to 2 different buckets, you will not find the key even if equals() method says they are equal.

hashcode() must be overriden to be consistent with your overridden equals() method. Only in this case it is guaranteed the proper, expected and consistent working of HashMap.

Read the javadoc of Object.hashcode() for the contract that you must not violate. The main point is that if equals() returns true for another object, the hashcode() method must return the same value for both of these objects.

icza
  • 389,944
  • 63
  • 907
  • 827
  • In the implementation I'm looking at, it always checks for an *exact* hash code match before calling `equals`, not just for the same bucket. – Jon Skeet Jul 21 '14 at 06:24
  • @JonSkeet Implementation may be differnet based on vendor and on version. – icza Jul 21 '14 at 06:25
  • Agreed, although I don't think I've ever seen an implementation which *just* goes by bucket. (Checking that the hash code matches exactly is very cheap, and could easily avoid more expensive `equals` calls.) Which implementation are you looking at? – Jon Skeet Jul 21 '14 at 07:10
  • @JonSkeet I saw this in the past and I agree that it is cheap to check hashcodes first, especially good if load factor is high. But here's an implementation which only calls `equals()` inside the bucket: http://developer.classpath.org/doc/java/util/HashMap-source.html – icza Jul 21 '14 at 07:28
  • Interesting, thanks. You might want to edit your answer to indicate that it talks about one *possible* implementation. – Jon Skeet Jul 21 '14 at 07:52
0

Can you explain why it didn't fail in the second case?

In a nutshell, it is not guaranteed to fail. The two objects in the second example could end up having the same hash code (purely by coincidence or, more likely, due to compiler optimizations or due to how the default hashCode() works in your JVM). This would lead to the behaviour you describe.

For what it's worth, I cannot reproduce this behaviour with my compiler/JVM.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
0

In your case by coincidence JVM was able to find the same hashCode for both object. When I ran your code, in my JVM it gave null for both the case. So your problem is because of JVM not the code.

It is better to override hashCode() each and every time when you override equils() method. I haven't read Effective Java, I read SCJP by Kathy Sierra. So if you need more details then you can read this book. It's nice.

hcl
  • 64
  • 5
  • I wanted to say that it is because of JVM. Because in my machine it is working fine. "Can you explain why it didn't fail in the second case?" What is your thinking about this problem? – hcl Jul 21 '14 at 08:15
  • I haven't take that personally. Just saying. Feel free to give any suggestion when I am wrong. – hcl Jul 21 '14 at 08:37
  • Its good to have such spontaneous user in stackoverflow, welcome aboard :) – MarmiK Jul 21 '14 at 09:15
  • Who down vote your answer I dont know, but want to say please add comment for what reason you are down voting. – Jayesh Jul 22 '14 at 05:01
0

Your last code snipped does not compile because you haven't declared phoneBook.

Both main methods should work exactly the same. There is a 1 in 16 chance that it will print Jenny because a newly crated HashMap has a default size of 16. In detail that means that only the lower 4 bits of the hashCode will be checked. If they equal the equal method is used.

TomWolk
  • 968
  • 10
  • 13