简体   繁体   中英

Should to objects which have the same hash be equal?

In this below example, I create two objects which have exactly the same internal structure. Both carry nothing but the value 1 as an instance variable. My thinking is that if I take the hash of e1 it should be the same as the hash of e2 and therefore e1.equals(e2) should return true.

class EqualsChecker {

    public static void main(String[] args) {

        Elem e1 = new Elem(1);
        Elem e2 = new Elem(1);


        System.out.println(e1);                                // EqualsChecker$Elem@6ff3c5b5
        System.out.println(e2);                                // EqualsChecker$Elem@3764951d
        System.out.println("e1.equals(e2): " + e1.equals(e2)); // returns false
    }


    static class Elem {
        private int v;
        public Elem(int i) {
            this.v = i;
        }   
    }   
}

Why does equals return false here? I think I have the middle case in the below sketch: 在此处输入图片说明

equals(Object) 's default implementation checks if the two objects are the same instance (ie they are == ). If you want some different logic, you'll have to implement it yourself. Note that if you do this, you should also implement your own hashCode() , so that two objects that are equal will also have matching hash codes. Eg:

class Elem {
    private int v;

    @Override
    public boolean equals(final Object o) {
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        Elem elem = (Elem) o;
        return this.v == elem.v;
    }

    @Override
    public int hashCode() {
        return this.v;
    }
}

Look at the following points from https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#hashCode--

  1. Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
  2. 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.
  3. It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
  4. As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (The hashCode may or may not be implemented as some function of an object's memory address at some point in time.)

Now, look at the following code and its output:

class MyEmployee {
    String code;
    String name;
    int age;

    public MyEmployee(String code, String name, int age) {
        super();
        this.code = code;
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee3 = employee1;
        System.out.println(employee1.equals(employee3));
        System.out.println("employee1.hashCode(): " + employee1.hashCode());
        System.out.println("employee3.hashCode(): " + employee3.hashCode());
        System.out.println(employee1.equals(employee2));
        System.out.println("employee2.hashCode(): " + employee2.hashCode());
    }
}

Output:

true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
false
employee2.hashCode(): 1297685781

Since employee3 is pointing to the same object as employee1 , you are getting the same hashcode for them while employee2 is pointing to a different object (although it has the same content, the keyword, new will create a separate object in the memory) and therefore, you may rarely get the same hashcode for employee2 as point#4 mentioned above from the documentation states: As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects.

You must override hashCode method in a way which should return the same hashcode for two objects having the same content eg

class MyEmployee {
    String code;
    String name;
    int age;

    public MyEmployee(String code, String name, int age) {
        super();
        this.code = code;
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((code == null) ? 0 : code.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }   
}

public class Main {
    public static void main(String[] args) {
        MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee3 = employee1;
        System.out.println(employee1.equals(employee3));
        System.out.println("employee1.hashCode(): " + employee1.hashCode());
        System.out.println("employee3.hashCode(): " + employee3.hashCode());
        System.out.println(employee1.equals(employee2));
        System.out.println("employee2.hashCode(): " + employee2.hashCode());
    }
}

Output:

true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
false
employee2.hashCode(): 128107556

The implementation of hashCode given above produces the same hashcode for employee1 and employee2 even though equals returns false (check as point#3 mentioned above from the documentation).

A wrong way of overriding of hashCode may result in even the same objects returning different hashcodes eg

class MyEmployee {
    String code;
    String name;
    int age;

    public MyEmployee(String code, String name, int age) {
        super();
        this.code = code;
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((code == null) ? 0 : (int) (code.length() * (Math.random() * 100)));
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee3 = employee1;
        System.out.println(employee1.equals(employee3));
        System.out.println("employee1.hashCode(): " + employee1.hashCode());
        System.out.println("employee1.hashCode() again: " + employee1.hashCode());
        System.out.println("employee3.hashCode(): " + employee3.hashCode());
        System.out.println(employee1.equals(employee2));
        System.out.println("employee2.hashCode(): " + employee2.hashCode());
    }
}

Output:

true
employee1.hashCode(): 66066760
employee1.hashCode() again: 66069457
employee3.hashCode(): 66073797
false
employee2.hashCode(): 66074882

This is the wrong way of overriding hashCode because invoking hashCode on the same object more than once during an execution of a Java application must consistently return the same integer (check as point#1 mentioned above from the documentation).

Now, look at the following code and its output:

class MyEmployee {
    String code;
    String name;
    int age;

    public MyEmployee(String code, String name, int age) {
        super();
        this.code = code;
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MyEmployee other = (MyEmployee) obj;
        if (code == null) {
            if (other.code != null)
                return false;
        } else if (!code.equals(other.code))
            return false;
        return true;
    }
}

public class Main {
    public static void main(String[] args) {
        MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee3 = employee1;
        System.out.println(employee1.equals(employee3));
        System.out.println("employee1.hashCode(): " + employee1.hashCode());
        System.out.println("employee3.hashCode(): " + employee3.hashCode());
        System.out.println(employee1.equals(employee2));
        System.out.println("employee2.hashCode(): " + employee2.hashCode());
    }
}

Output:

true
employee1.hashCode(): 511833308
employee3.hashCode(): 511833308
true
employee2.hashCode(): 1297685781

Since employee1.equals(employee2) returns true , the hashcode should also be returned same (check point#2 mentioned above from the documentation). However, the hashcode values of employee1 and employee2 are different which is not correct. This difference is because we haven't overridden the hashCode method. So, whenever you override equals , you should also override hashCode in a correct way.

Finally, given below is a correct way of implementing hashCode and equals :

class MyEmployee {
    String code;
    String name;
    int age;

    public MyEmployee(String code, String name, int age) {
        super();
        this.code = code;
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + ((code == null) ? 0 : code.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MyEmployee other = (MyEmployee) obj;
        if (age != other.age)
            return false;
        if (code == null) {
            if (other.code != null)
                return false;
        } else if (!code.equals(other.code))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

public class Main {
    public static void main(String[] args) {
        MyEmployee employee1 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee2 = new MyEmployee("AB12", "Dhruv", 24);
        MyEmployee employee3 = employee1;
        System.out.println(employee1.equals(employee3));
        System.out.println("employee1.hashCode(): " + employee1.hashCode());
        System.out.println("employee3.hashCode(): " + employee3.hashCode());
        System.out.println(employee1.equals(employee2));
        System.out.println("employee2.hashCode(): " + employee2.hashCode());
    }
}

Output:

true
employee1.hashCode(): 128107556
employee3.hashCode(): 128107556
true
employee2.hashCode(): 128107556

You need to override the equals method, otherwise the Object 's equals method will be used to compare the two instances.

@Override
public boolean equals(Object that) {
    if (this == that) return true;
    if (that instanceof Elem) {
        Elem thatElem = (Elem) that;
        return thatElem.v == this.v;
    }
    return false;
}

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