簡體   English   中英

具有相同哈希值的對象應該相等嗎?

[英]Should to objects which have the same hash be equal?

在下面的示例中,我創建了兩個具有完全相同內部結構的對象。 兩者都只攜帶值 1 作為實例變量。 我的想法是,如果我取e1的哈希值,它應該與e2的哈希值相同,因此e1.equals(e2)應該返回 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;
        }   
    }   
}

為什么這里的equals返回 false? 我想我在下面的草圖中有中間情況: 在此處輸入圖片說明

equals(Object)的默認實現檢查兩個對象是否是同一個實例(即它們是== )。 如果你想要一些不同的邏輯,你必須自己實現它。 請注意,如果您這樣做,您還應該實現自己的hashCode() ,以便兩個相等的對象也將具有匹配的哈希碼。 例如:

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;
    }
}

https://docs.oracle.com/javase/9​​/docs/api/java/lang/Object.html#hashCode看以下幾點——

  1. 在 Java 應用程序執行期間,只要在同一個對象上多次調用它,hashCode 方法必須始終返回相同的整數,前提是在對象的 equals 比較中使用的信息沒有被修改。 該整數不需要從應用程序的一次執行到同一應用程序的另一次執行保持一致。
  2. 如果根據 equals(Object) 方法兩個對象相等,則對兩個對象中的每一個調用 hashCode 方法必須產生相同的整數結果。
  3. 如果根據 equals(java.lang.Object) 方法兩個對象不相等,則不需要對兩個對象中的每一個調用 hashCode 方法必須產生不同的整數結果。 但是,程序員應該意識到為不相等的對象生成不同的整數結果可能會提高哈希表的性能。
  4. 盡可能實用,類 Object 定義的 hashCode 方法確實為不同的對象返回不同的整數。 (在某個時間點,hashCode 可能會也可能不會被實現為對象內存地址的某個函數。)

現在,查看以下代碼及其輸出:

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());
    }
}

輸出:

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

由於employee3指向同一對象employee1 ,你所得到的相同的哈希碼為他們而employee2指向一個不同的對象(盡管它具有相同的內容,關鍵字, new會在內存中創建一個單獨的對象),因此,您可能很少從文檔狀態中獲得與上面提到的第 4 點相同的employee2哈希碼: As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects.

您必須以某種方式覆蓋hashCode方法,該方法應該為具有相同內容的兩個對象返回相同的哈希碼,例如

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());
    }
}

輸出:

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

上面給出的hashCode的實現為employee1employee2生成相同的hashcode,即使equals返回false (檢查文檔中提到的第3 點)。

覆蓋hashCode錯誤方式可能會導致即使相同的對象返回不同的哈希碼,例如

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());
    }
}

輸出:

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

這是覆蓋hashCode的錯誤方法,因為在 Java 應用程序執行期間多次調用同一個對象上的hashCode必須始終如一地返回相同的整數(檢查文檔中提到的第 1 點)。

現在,查看以下代碼及其輸出:

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());
    }
}

輸出:

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

由於employee1.equals(employee2)返回true ,哈希碼也應該返回相同的(檢查點#2上面提到的文檔)。 但是, employee1employee2的hashcode 值不同,這是不正確的。 這種差異是因為我們沒有覆蓋hashCode方法。 因此,每當您覆蓋equals ,您還應該以正確的方式覆蓋hashCode

最后,下面給出了實現hashCodeequals的正確方法:

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());
    }
}

輸出:

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

您需要覆蓋equals方法,否則將使用Objectequals方法來比較兩個實例。

@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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM