简体   繁体   中英

How come this equals & hashCode override does NOT cause an exception or error?

When I compile and run the code below, I get the following results:

o1==o2 ? true
Hash codes: 0 | 0
o1==o2 ? true
Hash codes: 1 | 8
o1==o2 ? true
Hash codes: 7 | 3
o1==o2 ? true
Hash codes: 68 | 10
o1==o2 ? true
Hash codes: 5 | 4

From what I've read, if two objects are equal, their hashCodes must also be equal. So, how does this code not cause an exception or error?

import java.io.*;
import java.lang.*;

public class EqualsAndHashCode {

    private int num1;
    private int num2;

    public EqualsAndHashCode(int num1, int num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    public static void main(String[] args) {
        for (int x=0; x < 5; x++) {
            EqualsAndHashCode o1 = new EqualsAndHashCode(x, x);
            EqualsAndHashCode o2 = new EqualsAndHashCode(x, x);
            System.out.println("o1==o2 ? " + o1.equals(o2));
            System.out.println("Hash codes: " + o1.hashCode() + " | " + o2.hashCode());
        }
    }

    public boolean equals(Object o) {
        return (this.getNum1() == ((EqualsAndHashCode)o).getNum1()) && (this.getNum2() == ((EqualsAndHashCode)o).getNum2());
    }

    public int hashCode() {
        return (int)(this.getNum1() / Math.random());
    }

    public int getNum1() { return num1; }
    public int getNum2() { return num2; }
}

The premise behind my question was the wording surrounding the hashCode contract ( http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#hashCode() ): 我的问题的前提是围绕hashCode合同的措辞( http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#hashCode() ):

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

I assumed that this rule would have been enforced by the JVM at compile or run time and I would have seen errors or exceptions right away when the contract was violated...

if two objects are equal, their hashCodes must also be equal

Above is a recommendation and is not mandated by JVM

The idea behind this recommendation is to have less collisions when storing elements in a hashed collection such as HashMap.

A very good article on the need of hashcode, rules for equals and hashcode, etc:

http://www.ibm.com/developerworks/java/library/j-jtp05273/index.html

Because the JVM does not check or validate that the method contract holds true. They're just methods, and they can return whatever they want.

However, any code which depends upon them supporting the method contract might or will fail. You will not be able to use your EqualsAndHashCode objects in a HashMap , for example. That will throw exceptions or will not return correct values in most cases.

This is the same thing with compareTo() and TreeMaps - compareTo() can return any int that it wants, but if it doesn't return a consistent ordering as defined by the method contract in the Comparable interface, then your TreeMap will throw exceptions as soon as it detects inconsistencies.

So, how does this code not cause an exception or error?

Well, breaking the contract of equals and hashcode, never throws an exception or error. It's just that you see weird behaviour, when you use the objects of those class in hash based collections, like - HashSet , or HashMap .

For example, if in your case, you use your class objects as key in a HashMap , then you might not be able to find that key again, when you try to fetch it. Because, then even if your keys are equal, their hashcodes might be different. And HashMap saerch for keys first using their hashcodes , and then using equals .

How could they possibly be the same given the fact that you are dividing by a Random number?

The typical approach is to use the hashCode values of the individual fields to build the hashCode of the object (if they aren't primitive, in this case they are). You also typically multiply by several prime numbers.

// adapted from Effective Java
public int hashCode() {
   int p = 17, q = 37;

   p = q * p + num1;
   p = q * p + num2;

   return p;
}

Use this for hasCose

 public int hashCode() {
    int result = num1;
    result = 31 * result + num2;
    return result;
}

Default implementation of hashcode() and equals() is inhertied from Object class by each of the class that you define. In order for your code to behave correctly, especially when it is used in data structures such as HashMap, it is important that you "should" override the default implementation that ensures that "If two instances of your class are equal, then they return same value when hashCode() method is called".

Definition of equality of two objects depends on domain concept their classes represent, and hence, only the author of the class is best suited to implement "equals" and "hashcode" methods. For example, two Employee objects are considered equal if they have same value for "employeeId" attribute. These two may be different instances, but in the realm of domain (say, Human Resources System), they are equal due to equality of their employee IDs. Now, the author of the Employee class should implement "equals" method that compares "employeeId" attributes and returns true if they are same. Similarly, the author should ensure that hashCode() of two Employee instances are same if their employee IDs are same.

And if you are worried about how to write hashCode that meets the above Java recommendation, then, you can generate the hashCode and equals using Eclipse.

Though it is only a recommendation "if two objects are equal, their hashCodes must also be equal", you should be aware of the fact that your code can start misbehaving if objects of your class are used in Set, Map, etc. if you don't create "equals" and "hashCode" methods that comply with this recommendation. Only time you would like to ignore this recommendation is when you are sure that your class will never be tested for equality. An example of such class can be a DAO class or a Service class which typically is instantiated and used as Singleton, and no one (in normal scenarios) compares two DAO or Service classes

The purpose of method contracts is in most cases to allow other code to assume that certain conditions will hold true. In particular, the purpose of the hashCode and equals contracts is to allow a collection to assume that if an object Foo has a particular hashcode (eg 24601), and a collection of objects Bar is known not to contain any objects with that hashcode, one may infer from that information that Bar doesn't contain Foo . As a bonus, if a collection of objects contains a variety of hashcodes including that of Foo , and if one has precalculated the hashcodes of all the objects in the collection, one may check the hashcode of each object against that of Bar before looking at the object itself. Comparing two objects' already-computed hash values will be fast, no matter how complicated the objects are.

For all this to work, it is imperative that an object which will report itself equal to another object must always have reported the same hash code as that other object. Because it is always possible to obey this rule, there is seldom any good reason to disobey it. Even if the only immutable characteristic of an object which would be used in determining equality is its type, it's still possible to obey the rule by having all objects of that type return the same hash value. Having objects which will always be different report different hash values may improve performance by several orders of magnitude, but given a choice between behavior which is slow but correct, and behavior which is fast but wrong, the former should generally be preferred.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM