0

Could someone explain the following behavior for .hashcode().

When working with the two classes below I get different values for .hashcode() calls. Now I am just trying to understand the reason behind this behavior. I'm working with the default implementation of .hashcode(). I am not overriding it nor do I want to use it for hashtables or hashmaps. ( I understand this is the one of the main reasons for hashcodes)

If I execute Main 10 times I would get the same value for contact3 hashcode. hashcode = 1555009629 in this case.

Now... If I execute the same Main 10 other times but comment out the call to hashcode in mergeContactData then the hashcode value in Main for contact3 would change to 41359092.

The two following questions talk about overriding hashcode() and explanations using maps and tables which is not the intended use for this question. This question simply about behavior of default implementation.

Reason behind JVM's default Object.HashCode() implementation

Why do I need to override the equals and hashCode methods in Java?

public class Main {

   public static void main(String[] args) {
      Contact contact = new Contact("Gustavo", "xxx", 5555555555L);
      Contact contact2 = new Contact("Gustavo", "xxx", 5555555555L);
      Contact contact3 = contact.mergeContactData(contact2);
      System.out.println("Hashcode for contact3: " + contact3.hashCode());

   }
}
import java.util.HashSet;
import java.util.Set;

public class Contact {

   private String name;
   private Set<String> emails = new HashSet<String>();
   private Set<String> phones = new HashSet<>();

   public Contact(String name) {
      this(name, null, 0);
   }

   public Contact(String name, String email) {
      this(name, email, 0);
   }

   public Contact(String name, long phone) {
      this(name, null, phone);
   }

   public Contact(String name, String email, long phone) {
      this.name = name;
      if (email != null) {
         this.emails.add((email));
      }

      if (phone < 1000000000) {
         System.out.println("Invalid Phone Number");
         return;
      }
      String phoneNumber = "";
      phoneNumber += "(" +
              String.valueOf(phone / 10000000);
      phone = phone % 10000000;
      phoneNumber += ")" + String.valueOf(phone / 10000);
      phone = phone % 10000;
      phoneNumber += "-" + String.valueOf(phone);

      this.phones.add(phoneNumber);
   }

   public String getName() {
      return this.name;
   }

   public Contact mergeContactData(Contact contact) {

      if (this.name.equals(contact.name)) {
         Contact newContact = new Contact(this.name);
         newContact.emails.addAll(this.emails);
         newContact.phones.addAll(this.phones);
         newContact.emails.addAll(contact.emails);

         System.out.println(newContact.hashCode());

         contact.phones.forEach(s -> newContact.phones.add(s));
         return newContact;
      } else {
         System.out.println("Not the same contact name");
         return this;
      }
   }

   @Override
   public String toString() {
      return this.name + " -> emails: " + this.emails + " Phone: " + this.phones;
   }


}
17
  • 1
    The answer to this may help: stackoverflow.com/questions/20339002/… Commented Oct 30, 2024 at 23:10
  • 1
    You are not overriding hashCode(), so it is using the implementation from Object, which is returning a fixed value per instance. The hashcode is different every time you run your program. Printing the hash code does not matter, the value is different simply because you are executing the program multiple times. Commented Oct 30, 2024 at 23:11
  • 1
    @GustavoPalomino The implementation of Object.hashCode() depends on the actual JVM you are using. I can confirm what you see with OpenJDK Runtime Environment 17.0.13_p11 (build 17.0.13+11) on a linux machine, but that doesn't mean that the hashCode() has to work that way everywhere, only what is defined on docs.oracle.com/javase/8/docs/api/java/lang/…. Commented Oct 30, 2024 at 23:55
  • 1
    @GustavoPalomino Again, it's implementation detail of the actual JVM you are using. You have to check the source code of the JVM how the hashcode for java.lang.Object is calculated. Commented Oct 31, 2024 at 0:25
  • 1
    It's quite possible that if a different number of objects are allocated first, objects will get different hash codes than they would otherwise. There are lots of places where objects could be allocated. Commented Oct 31, 2024 at 2:03

1 Answer 1

2

hashCode isn't magic, it's just a method, but one that is defined in java.lang.Object itself. If you don't override it, that's the one you get.

And that one returns, and I'm vastly oversimplifying here, 'the memory address' of the object. In other words, that default hashCode impl is different every time you start a JVM. It has nothing to do with whether or not you remark anything out. The merge method returns a new contact (why? Because it runs new Contact and returns what that produced), and all objects have a 'random' hashCode that is different every time you start the JVM. Just make a simple contact and print the hashCode of that, then rerun that code a few times. Different hashCode every time (or not - a JVM does not have to return a different value. What it returns is unspecified, and doesn't need to be).

A class gets to decide which things are and are not equal; the fact that 2 contact objects have the exact same field values does not imply they are equal; that is up to you (the author of Contact) to decide. Some concepts aren't equal even if they have the same data. For example, 2 file handles to the same file aren't the same handle. By default, no object is equal to anything other than itself, not even to other instances of the same type with the same contents.

And hashCode is simple: The rule is - if 2 objects are equal (as per .equals, then they must have the same hashCode() value. That's the only rule (and it does not work in reverse; 2 objects with the same hashcode do not necessarily have to be equal).

If your intent is for 2 instances of Contact to count as being 'equal', then you have to write an equals and hashCode method. Or have your IDE generate it, or use lombok to generate them, or use record.

Sign up to request clarification or add additional context in comments.

3 Comments

I agree with everything you have said. However, I'm not questioning equality of 2 objects. The hashcode value for contact3 in Main is the same every time I execute Main when the hash call in mergeData is commented out. If it is NOT commented out the hashcode value for contact3 is different than when it is commented out but still the same everytime I execute it while still not commented out.
The JVM makes no guarantee about how it computes the hashcode in the default Object implementation. That's the short answer. I am going to guess on a longer answer based on what @rzwitserloot says that it could be a memory location. Let's say the class/data uses 100 bytes of memory and you create 3 copies and they get allocated the next free memory, their addresses are 0, 100, 200. Now alter the code (by un/commenting that method) and your code is longer/shorter... meaning the memory addresses are different... QED.
It doesn't return the memory address. Oversimplifying is OK, but you're oversimplifying to the point that you perpetuate a severe misunderstanding of what the default hashcode is. See also What's the default hash value of an object on 64 bit JVM

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.