首页 文章

为什么我需要覆盖Java中的equals和hashCode方法?

提问于
浏览
298

最近我读了这篇Developer Works Document .

该文档是关于有效和正确地定义 hashCode()equals() ,但是我无法弄清楚为什么我们需要覆盖这两个方法 .

如何有效地实施这些方法?

29 回答

  • 2

    Joshua Bloch谈到Effective Java

    您必须覆盖覆盖equals()的每个类中的hashCode() . 如果不这样做,将导致违反Object.hashCode()的常规 Contract ,这将阻止您的类与所有基于散列的集合(包括HashMap,HashSet和Hashtable)一起正常运行 .

    让我们尝试理解它,如果我们覆盖 equals() 而不覆盖 hashCode() 并尝试使用 Map 会发生什么 .

    假设我们有一个这样的类,如果它们的 importantField 相等( hashCode()equals() 由eclipse生成),那么 MyClass 的两个对象是相等的

    public class MyClass {
    
        private final String importantField;
        private final String anotherField;
    
        public MyClass(final String equalField, final String anotherField) {
            this.importantField = equalField;
            this.anotherField = anotherField;
        }
    
        public String getEqualField() {
            return importantField;
        }
    
        public String getAnotherField() {
            return anotherField;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((importantField == null) ? 0 : importantField.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(final Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final MyClass other = (MyClass) obj;
            if (importantField == null) {
                if (other.importantField != null)
                    return false;
            } else if (!importantField.equals(other.importantField))
                return false;
            return true;
        }
    
    }
    

    Override only equals

    如果只覆盖了 equals ,那么当你调用 myMap.put(first,someValue) 时,首先会哈希到某个桶,当你调用 myMap.put(second,someOtherValue) 时,它会哈希到其他桶(因为它们有不同的 hashCode ) . 所以,虽然他们是平等的,但他们并没有意识到这一点,而且他们都留在了 Map 上 .


    虽然如果我们覆盖 hashCode() 则没有必要覆盖 equals() ,让我们看看在这种特殊情况下会发生什么,我们知道 MyClass 的两个对象是相等的,如果它们的 importantField 相等但我们不会覆盖 equals() .

    Override only hashCode

    想象一下,你有这个

    MyClass first = new MyClass("a","first");
    MyClass second = new MyClass("a","second");
    

    如果您只覆盖 hashCode ,那么当您调用 myMap.put(first,someValue) 时,它首先计算它的 hashCode 并将其存储在给定的存储桶中 . 然后当你调用 myMap.put(second,someOtherValue) 时,它应该根据Map Documentation首先替换为秒,因为它们是相等的(根据业务需求) .

    但问题是equals没有被重新定义,所以当 Map 哈希 second 并在桶中迭代看是否有一个对象 k 这样 second.equals(k) 为真时它将找不到任何因为 second.equals(first) 将是 false .

    希望很清楚

  • 19

    诸如 HashMapHashSet 之类的集合使用对象的哈希码值来确定它应该如何存储在集合中,并且再次使用哈希码以便在其集合中定位对象 .

    散列检索分为两步:

    • 找到合适的桶(使用 hashCode()

    • 在存储桶中搜索正确的元素(使用 equals()

    这是一个小例子,说明为什么我们应该重写 equals()hashcode() .

    考虑一个 Employee 类,它有两个字段:age和name .

    public class Employee {
    
        String name;
        int age;
    
        public Employee(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (!(obj instanceof Employee))
                return false;
            Employee employee = (Employee) obj;
            return employee.getAge() == this.getAge()
                    && employee.getName() == this.getName();
        }
    
        // commented    
        /*  @Override
            public int hashCode() {
                int result=17;
                result=31*result+age;
                result=31*result+(name!=null ? name.hashCode():0);
                return result;
            }
         */
    }
    

    现在创建一个类,将 Employee 对象插入 HashSet 并测试该对象是否存在 .

    public class ClientTest {
        public static void main(String[] args) {
            Employee employee = new Employee("rajeev", 24);
            Employee employee1 = new Employee("rajeev", 25);
            Employee employee2 = new Employee("rajeev", 24);
    
            HashSet<Employee> employees = new HashSet<Employee>();
            employees.add(employee);
            System.out.println(employees.contains(employee2));
            System.out.println("employee.hashCode():  " + employee.hashCode()
            + "  employee2.hashCode():" + employee2.hashCode());
        }
    }
    

    它将打印以下内容:

    false
    employee.hashCode():  321755204  employee2.hashCode():375890482
    

    现在取消注释 hashcode() 方法,执行相同的操作,输出将是:

    true
    employee.hashCode():  -938387308  employee2.hashCode():-938387308
    

    现在你能看出为什么如果两个对象被认为是相等的,它们的哈希码也必须相等?否则,你永远无法找到对象,因为类Object中的默认哈希码方法实际上总是为每个对象提供一个唯一的编号,即使 equals() 方法被覆盖的方式考虑了两个或多个对象等于 . 如果对象的哈希码没有反映出来,那么它们的平等程度并不重要 . 所以再一次:如果两个对象相等,它们的哈希码也必须相等 .

  • -1

    您必须覆盖覆盖equals()的每个类中的hashCode() . 如果不这样做,将导致违反Object.hashCode()的常规 Contract ,这将阻止您的类与所有基于散列的集合(包括HashMap,HashSet和Hashtable)一起正常运行 . 来自Effective Java,来自Joshua Bloch

    通过一致地定义 equals()hashCode() ,您可以提高类的可用性,使其成为基于散列的集合中的键 . 正如hashCode的API文档所解释的那样:“这种方法受到哈希表的支持,例如 java.util.Hashtable 提供的哈希表 . ”

    关于如何有效实施这些方法的问题的最佳答案是建议您阅读Effective Java的第3章 .

  • 11

    简单地说,Object中的equals-method检查引用相等性,其中当属性相等时,类的两个实例在语义上仍然相等 . 例如,将对象放入使用equals和hashcode的容器(例如HashMapSet)时,这很重要 . 假设我们有一个类:

    public class Foo {
        String id;
        String whatevs;
    
        Foo(String id, String whatevs) {
            this.id = id;
            this.whatevs = whatevs;
        }
    }
    

    我们使用相同的 id 创建两个实例:

    Foo a = new Foo("id", "something");
    Foo b = new Foo("id", "something else");
    

    没有压倒平等,我们得到:

    • a.equals(b)是假的,因为它们是两个不同的实例

    • a.equals(a)是真的,因为它是同一个实例

    • b.equals(b)是真的,因为它是同一个实例

    正确?好吧,也许,如果这是你想要的 . 但是,让我们假设我们希望具有相同id的对象成为同一个对象,无论它是两个不同的实例 . 我们重写equals(和hashcode):

    public class Foo {
        String id;
        String whatevs;
    
        Foo(String id, String whatevs) {
            this.id = id;
            this.whatevs = whatevs;
        }
    
        @Override
        public boolean equals(Object other) {
            if (other instanceof Foo) {
                return ((Foo)other).id.equals(this.id);   
            }
        }
    
        @Override
        public int hashCode() {
            return this.id.hashCode();
        }
    }
    

    至于实现equals和hashcode我可以推荐使用Guava's helper methods

  • 2

    Identity is not equality.

    • 等于operator == 测试标识 .

    • equals(Object obj) 方法比较等式测试(即我们需要通过覆盖方法来告诉相等)

    为什么我需要覆盖Java中的equals和hashCode方法?

    First we have to understand the use of equals method.

    为了识别两个对象之间的差异,我们需要覆盖equals方法 .

    For example:

    Customer customer1=new Customer("peter");
    Customer customer2=customer1;
    customer1.equals(customer2); // returns true by JVM. i.e. both are refering same Object
    ------------------------------
    Customer customer1=new Customer("peter");
    Customer customer2=new Customer("peter");
    customer1.equals(customer2); //return false by JVM i.e. we have two different peter customers.
    
    ------------------------------
    Now I have overriden Customer class equals method as follows:
     @Override
        public boolean equals(Object obj) {
            if (this == obj)   // it checks references
                return true;
            if (obj == null) // checks null
                return false;
            if (getClass() != obj.getClass()) // both object are instances of same class or not
                return false;
            Customer other = (Customer) obj;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name)) // it again using bulit in String object equals to identify the difference 
                return false;
            return true; 
        }
    Customer customer1=new Customer("peter");
    Customer customer2=new Customer("peter");
    Insteady identify the Object equality by JVM, we can do it by overring equals method.
    customer1.equals(customer2);  // returns true by our own logic
    

    Now hashCode method can understand easily.

    hashCode生成整数,以便将对象存储在数据结构中,如 HashMapHashSet .

    假设我们如上所述覆盖 Customer 的equals方法,

    customer1.equals(customer2);  // returns true by our own logic
    

    在我们将对象存储在存储桶中时使用数据结构(存储桶是文件夹的奇特名称) . 如果我们使用内置哈希技术,对于上面两个客户,它会生成两个不同的哈希码 . 所以我们将相同的对象存储在两个不同的地方 . 为了避免这种问题,我们应该基于以下原则覆盖hashCode方法 .

    • 不相等的实例可能具有相同的哈希码 .

    • 个相等的实例应该返回相同的哈希码 .

  • 3

    好的,让我用非常简单的话来解释这个概念 .

    首先,从更广泛的角度来看,我们有集合,而hashmap是集合中的数据结构之一 .

    要理解为什么我们必须覆盖equals和hashcode方法,如果需要首先了解什么是hashmap以及做什么 .

    散列映射是一种数据结构,它以数组方式存储数据的键值对 . 让我们说a [],其中'a'中的每个元素都是一个键值对 .

    此外,上述数组中的每个索引都可以是链表,从而在一个索引处具有多个值 .

    现在为什么要使用hashmap?如果我们必须在一个大型数组中进行搜索,那么搜索每个它们将不会有效,那么什么哈希技术告诉我们让我们用一些逻辑预处理数组并根据该逻辑对元素进行分组,即哈希

    例如:我们有数组1,2,3,4,5,6,7,8,9,10,11,我们应用一个散列函数mod 10,因此1,11将被分组在一起 . 因此,如果我们必须在先前的数组中搜索11,那么我们将不得不迭代整个数组,但是当我们对它进行分组时,我们限制了迭代的范围,从而提高了速度 . 为简单起见,用于存储所有上述信息的数据结构可以被认为是2d数组

    现在除了上面的hashmap之外还告诉它不会在其中添加任何Duplicates . 这是我们必须覆盖equals和hashcode的主要原因

    所以当它说解释hashmap的内部工作时,我们需要找到hashmap有哪些方法,以及它如何遵循我上面解释的上述规则

    所以hashmap有一个叫做put(K,V)的方法,根据hashmap它应该遵循上面有效分配数组的规则而不添加任何重复

    所以put会做的是它首先会为给定的密钥生成哈希码,以决定该值应该进入哪个索引 . 如果该索引中没有任何内容,则新值将添加到那里,如果那里已存在某些内容然后应该在该索引的链表结束后添加新值 . 但请记住,不应根据hashmap的所需行为添加重复项 . 所以假设你有两个整数对象aa = 11,bb = 11 . 作为从对象类派生的每个对象,比较两个对象的默认实现是它比较对象内部的引用而不是值 . 因此,在上述情况下,虽然在语义上相等但都会使相等性测试失败,并且存在两个具有相同哈希码和相同值的对象从而产生重复的可能性 . 如果我们覆盖,那么我们可以避免添加重复 . 你也可以参考Detail working

    import java.util.HashMap;
    
    
    public class Employee {
    
    String name;
    String mobile;
    public Employee(String name,String mobile) {
        this.name=name;
        this.mobile=mobile;
    }
    
    @Override
    public int hashCode() {
        System.out.println("calling hascode method of Employee");
        String str=this.name;
        Integer sum=0;
        for(int i=0;i<str.length();i++){
            sum=sum+str.charAt(i);
        }
        return sum;
    
    }
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        System.out.println("calling equals method of Employee");
        Employee emp=(Employee)obj;
        if(this.mobile.equalsIgnoreCase(emp.mobile)){
    
            System.out.println("returning true");
            return true;
        }else{
            System.out.println("returning false");
            return false;
        }
    
    
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
    
        Employee emp=new Employee("abc", "hhh");
        Employee emp2=new Employee("abc", "hhh");
        HashMap<Employee, Employee> h=new HashMap<>();
        //for (int i=0;i<5;i++){
            h.put(emp, emp);
            h.put(emp2, emp2);
    
        //}
    
        System.out.println("----------------");
        System.out.println("size of hashmap: "+h.size());
    
    
    }
    
    }
    
  • 14

    hashCode()

    如果只覆盖哈希码方法,则不会发生任何事情 . 因为它总是为每个对象返回新的 hashCode 作为Object类 .

    equals()

    如果你只覆盖相等的方法, a.equals(b) 为真,则意味着a和b的 hashCode 必须相同但不会发生 . 因为你没有覆盖 hashCode 方法 .

    注意:对象类的 hashCode() 方法总是为每个对象返回新的 hashCode .

    因此,当您需要在基于散列的集合中使用对象时,必须覆盖 equals()hashCode() .

  • 442

    为了使用我们自己的类对象作为HashMap,Hashtable等集合中的键,我们应该通过对集合的内部工作有所了解来覆盖这两个方法(hashCode()和equals()) . 否则,它会导致我们不期望的错误结果 .

  • 3

    因为如果你不覆盖它们,你将使用Object中的默认实现 .

    鉴于实例相等和hascode值通常需要知道构成对象的内容,通常需要它们在你的 class 中重新定义,以具有任何实际意义 .

  • 5

    添加到@Lombo的答案

    When will you need to override equals() ?

    Object的equals()的默认实现是

    public boolean equals(Object obj) {
            return (this == obj);
    }
    

    这意味着只有当两个对象具有相同的内存地址时才会被认为是相同的,只有在您将对象与自身进行比较时才会成立 .

    但是如果两个对象的一个或多个属性具有相同的值,则可能需要考虑两个对象(请参阅@Lombo答案中给出的示例) .

    所以你会在这些情况下覆盖 equals() ,你会给自己平等的条件 .

    I have successfully implemented equals() and it is working great.So why are they asking to override hashCode() as well?

    好吧 . 只要您不在用户定义的类上使用基于"Hash"的集合,就可以了 . 但是在将来的某个时候你可能想要使用 HashMapHashSet ,如果你没有使用 override 和"correctly implement" hashCode(),这些基于哈希的集合将无法按预期工作 .

    Override only equals (Addition to @Lombo 's answer)

    myMap.put(first,someValue)
    myMap.contains(second); --> But it should be the same since the key are the same.But returns false!!! How?
    

    首先,HashMap检查 second 的hashCode是否与 first 相同 . 仅当值相同时,才会继续检查同一存储桶中的相等性 .

    但是这里的hashCode对于这两个对象是不同的(因为它们具有不同的内存地址 - 来自默认实现) . 因此,它甚至不会检查是否平等 .

    如果在重写的equals()方法中有一个断点,如果它们有不同的hashCodes,它就不会介入 . contains() 检查 hashCode() 并且只有它们相同才会调用 equals() 方法 .

    Why can't we make the HashMap check for equality in all the buckets? So there is no necessity for me to override hashCode() !!

    那你就错过了基于Hash的集合 . 考虑以下 :

    Your hashCode() implementation : intObject%9.
    

    以下是以桶形式存储的密钥 .

    Bucket 1 : 1,10,19,... (in thousands)
    Bucket 2 : 2,20,29...
    Bucket 3 : 3,21,30,...
    ...
    

    说,你想知道 Map 是否包含密钥10.你想要搜索所有的桶吗?或者你想只搜索一个桶吗?

    基于hashCode,您将确定如果存在10,则它必须存在于Bucket 1中 . 因此只会搜索Bucket 1!

  • 6
    class A {
        int i;
        // Hashing Algorithm
        if even number return 0 else return 1
        // Equals Algorithm,
        if i = this.i return true else false
    }
    
    • put('key','value')将使用 hashCode() 计算哈希值以确定存储桶并使用 equals() 方法查找该值是否已存在于存储桶中 . 如果没有,它将被添加,否则它将被替换为当前值

    • get('key')将首先使用 hashCode() 查找条目(存储桶),然后使用 equals() 查找条目中的值

    如果两者都被覆盖,

    Map < A >

    Map.Entry 1 --> 1,3,5,...
    Map.Entry 2 --> 2,4,6,...
    

    如果不等于等于

    Map < A >

    Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
    Map.Entry 2 --> 2,4,6,...,2,4,..
    

    如果没有覆盖hashCode

    Map < A >

    Map.Entry 1 --> 1
    Map.Entry 2 --> 2
    Map.Entry 3 --> 3
    Map.Entry 4 --> 1
    Map.Entry 5 --> 2
    Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
    So on...
    

    HashCode平等 Contract

    • 根据相等方法相等的两个键应该生成相同的hashCode

    • 生成相同hashCode的两个密钥不必相等(在上面的示例中,所有偶数生成相同的哈希代码)

  • 10

    Java提出了一条规则

    “如果两个对象使用Object类equals方法相等,那么hashcode方法应该为这两个对象赋予相同的值 . ”

    所以,如果在我们的类中我们覆盖 equals() ,我们应该覆盖 hashcode() 方法也遵循这个规则 . 例如, equals()hashcode() 两种方法都在 Hashtable 中用于将值存储为键值对 . 如果我们覆盖一个而不是另一个,那么如果我们使用这样的对象作为键, Hashtable 可能无法正常工作 .

  • 4

    考虑在桶中收集球,全部为黑色 . 你的工作是为这些球着色如下,并用它来进行适当的比赛,

    对于网球 - 黄色,红色 . 对于板球 - 白色

    现在桶有黄色,红色和白色三种颜色的球 . 现在 you did the coloring Only you know which color is for which game.

    着色球 - 哈希 . 为比赛选择球 - 等于 .

    如果你做了着色并且有人选择球用于板球或网球,他们不会介意颜色!

  • 1

    我正在查看解释“如果你只覆盖hashCode然后当你调用 myMap.put(first,someValue) 它首先,计算它的hashCode并将其存储在给定的存储桶中 . 然后当你调用 myMap.put(first,someOtherValue) 时,它应该首先替换为映射文档的第二个,因为它们是平等的(根据我们的定义) . “ :

    我认为第二次加入 myMap 然后它应该是'second'对象,如 myMap.put(second,someOtherValue)

  • 5

    使用Value Objects时非常有用 . 以下是Portland Pattern Repository的摘录:

    值对象的示例包括数字,日期,货币和字符串 . 通常,它们是相当广泛使用的小物体 . 他们的身份是基于他们的状态而不是他们的对象身份 . 这样,您可以拥有相同概念值对象的多个副本 . 所以我可以拥有一个代表1998年1月16日日期的对象的多个副本 . 这些副本中的任何一个都将彼此相等 . 对于像这样的小对象,通常更容易创建新对象并移动它们而不是依靠单个对象来表示日期 . 值对象应始终覆盖Java中的.equals()(或者在Smalltalk中) . (记得也要覆盖.hashCode() . )

  • 4

    方法等于和hashcode是在对象类中定义 . 默认情况下,如果equals方法返回true,则系统将进一步检查哈希码的值 . 如果2个对象的哈希码也相同,则对象将被视为相同 . 因此,如果仅覆盖equals方法,则即使重写的equals方法指示2个对象相等,系统定义的哈希码也可能不表示2个对象相等 . 所以我们也需要覆盖哈希码 .

  • 1

    Equals and Hashcode methods in Java

    它们是java.lang.Object类的方法,它是所有类的超类(自定义类以及java API中定义的其他类) .

    Implementation:

    public boolean equals(Object obj)public int hashCode()

    enter image description here

    public boolean equals(Object obj)

    此方法仅检查两个对象引用x和y是否引用同一对象 . 即它检查x == y .

    它是自反的:对于任何参考值x,x.equals(x)应该返回true .

    它是对称的:对于任何参考值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true .

    它是传递性的:对于任何参考值x,y和z,如果x.equals(y)返回true而y.equals(z)返回true,则x.equals(z)应返回true .

    它是一致的:对于任何参考值x和y,x.equals(y)的多次调用始终返回true或始终返回false,前提是没有修改在对象上的equals比较中使用的信息 .

    对于任何非空引用值x,x.equals(null)应返回false .

    public int hashCode()

    此方法返回调用此方法的对象的哈希码值 . 此方法将哈希代码值作为整数返回,并且支持基于哈希的集合类(如Hashtable,HashMap,HashSet等) . 必须在覆盖equals方法的每个类中重写此方法 .

    The general contract of hashCode is:

    每当在Java应用程序的执行期间多次在同一对象上调用它时,hashCode方法必须始终返回相同的整数,前提是不修改对象的equals比较中使用的信息 .

    从应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致 .

    如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果 .

    如果两个对象根据equals(java.lang.Object)方法不相等,则不需要在两个对象中的每一个上调用hashCode方法必须生成不同的整数结果 . 但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能 .

    等对象必须产生相同的哈希码,只要它们相等,但是不对等的对象不需要产生不同的哈希码 .

    资源:

    JavaRanch

    Picture

  • 2

    假设你有一个聚合另外两个(B)(C)的类(A),你需要在哈希表中存储(A)的实例 . 默认实现仅允许区分实例,但不允许区分(B)和(C) . 因此A的两个实例可能相等,但默认情况下不允许您以正确的方式比较它们 .

  • 48

    在下面的示例中,如果在Person类中注释掉equals或hashcode的覆盖,则此代码将无法查找Tom的顺序 . 使用哈希码的默认实现可能导致哈希表查找失败 .

    我在下面的内容是一个简化的代码,用于提升人们的订单 . Person被用作哈希表中的键 .

    public class Person {
        String name;
        int age;
        String socialSecurityNumber;
    
        public Person(String name, int age, String socialSecurityNumber) {
            this.name = name;
            this.age = age;
            this.socialSecurityNumber = socialSecurityNumber;
        }
    
        @Override
        public boolean equals(Object p) {
            //Person is same if social security number is same
    
            if ((p instanceof Person) && this.socialSecurityNumber.equals(((Person) p).socialSecurityNumber)) {
                return true;
            } else {
                return false;
            }
    
        }
    
        @Override
        public int hashCode() {        //I am using a hashing function in String.java instead of writing my own.
            return socialSecurityNumber.hashCode();
        }
    }
    
    
    public class Order {
        String[]  items;
    
        public void insertOrder(String[]  items)
        {
            this.items=items;
        }
    
    }
    
    
    
    import java.util.Hashtable;
    
    public class Main {
    
        public static void main(String[] args) {
    
           Person p1=new Person("Tom",32,"548-56-4412");
            Person p2=new Person("Jerry",60,"456-74-4125");
            Person p3=new Person("Sherry",38,"418-55-1235");
    
            Order order1=new Order();
            order1.insertOrder(new String[]{"mouse","car charger"});
    
            Order order2=new Order();
            order2.insertOrder(new String[]{"Multi vitamin"});
    
            Order order3=new Order();
            order3.insertOrder(new String[]{"handbag", "iPod"});
    
            Hashtable<Person,Order> hashtable=new Hashtable<Person,Order>();
            hashtable.put(p1,order1);
            hashtable.put(p2,order2);
            hashtable.put(p3,order3);
    
           //The line below will fail if Person class does not override hashCode()
           Order tomOrder= hashtable.get(new Person("Tom", 32, "548-56-4412"));
            for(String item:tomOrder.items)
            {
                System.out.println(item);
            }
        }
    }
    
  • 0

    1) The common mistake is shown in the example below.

    public class Car {
    
        private String color;
    
        public Car(String color) {
            this.color = color;
        }
    
        public boolean equals(Object obj) {
            if(obj==null) return false;
            if (!(obj instanceof Car))
                return false;   
            if (obj == this)
                return true;
            return this.color.equals(((Car) obj).color);
        }
    
        public static void main(String[] args) {
            Car a1 = new Car("green");
            Car a2 = new Car("red");
    
            //hashMap stores Car type and its quantity
            HashMap<Car, Integer> m = new HashMap<Car, Integer>();
            m.put(a1, 10);
            m.put(a2, 20);
            System.out.println(m.get(new Car("green")));
        }
    }
    

    找不到绿色的汽车

    2. Problem caused by hashCode()

    问题是由未重写的方法 hashCode() 引起的 . equals()hashCode() 之间的 Contract 是:

    • 如果两个对象相等,则它们必须具有相同的哈希码 .

    • 如果两个对象具有相同的哈希码,则它们可能相等也可能不相等 .

    public int hashCode(){  
      return this.color.hashCode(); 
    }
    
  • 2

    String类和包装类具有与Object类不同的 equals()hashCode() 方法的实现 . Object类的equals()方法比较对象的引用,而不是内容 . Object类的hashCode()方法为每个单个对象返回不同的哈希码,无论内容是否相同 .

    当您使用Map集合并且密钥是Persistent类型,StringBuffer / builder类型时,它会导致问题 . 由于它们不像String类那样重写equals()和hashCode(),因此即使两个具有相同的内容,equals()也会在比较两个不同的对象时返回false . 它将使hashMap存储相同的内容密钥 . 存储相同的内容键意味着它违反了Map规则,因为Map根本不允许重复键 . 因此,您在类中重写equals()以及hashCode()方法并提供实现(IDE可以生成这些方法),以便它们与String的equals()和hashCode()相同,并防止相同的内容键 .

    您必须覆盖hashCode()方法以及equals()因为equals()根据哈希码工作 .

    此外,重写hashCode()方法和equals()有助于完整的equals() - hashCode() Contract :“如果两个对象相等,那么它们必须具有相同的哈希码 . ”

    When do you need to write custom implementation for hashCode()?

    我们知道HashMap的内部工作原则是Hashing . 存在一些存储入口集的桶 . 您可以根据需要自定义hashCode()实现,以便可以将相同的类别对象存储到同一索引中 . 当您使用 put(k,v) 方法将值存储到Map集合时,put()的内部实现是:

    put(k, v){
    hash(k);
    index=hash & (n-1);
    }
    

    意味着,它生成索引,并根据特定密钥对象的哈希码生成索引 . 因此,使此方法根据您的要求生成哈希码,因为相同的哈希码入口集将存储在同一个桶或索引中 .

    而已!

  • 0

    这背后的原因:当你的对象字段可以为null时,实现Object.equals会很麻烦,因为你必须单独检查null . 使用Objects.equal允许您以null敏感的方式执行equals检查,而不会冒NullPointerException的风险 . Objects.equal("a", "a"); // returns true Objects.equal(null, "a"); // returns false Objects.equal("a", null); // returns false Objects.equal(null, null); // returns true

  • -4

    hashCode() 方法用于获取给定对象的唯一整数 . 此整数用于确定存储桶位置,此对象需要存储在某些 HashTableHashMap 之类的数据结构中 . 默认情况下,Object的 hashCode() 方法返回存储对象的内存地址的整数表示 .

    当我们将对象插入 HashTableHashMapHashSet 时,会使用 hashCode() 对象方法 . 更多关于维基百科上的 HashTables 以供参考 .

    要在 Map 数据结构中插入任何条目,我们需要键和值 . 如果键和值都是用户定义数据类型,则键的 hashCode() 将确定内部存储对象的位置 . 当还需要从 Map 中查找对象时,密钥的哈希码将确定搜索对象的位置 .

    哈希码仅在内部指向某个"area"(或列表,存储桶等) . 由于不同的密钥对象可能具有相同的哈希码,因此哈希码本身并不能保证找到正确的密钥 . 然后 HashTable 迭代此区域(具有相同哈希码的所有键)并使用键的 equals() 方法查找正确的键 . 找到正确的密钥后,将返回为该密钥存储的对象 .

    因此,正如我们所看到的,在存储和查找 HashTable 中的对象时,会使用 hashCode()equals() 方法的组合 .

    笔记:

    • 始终使用对象的相同属性来生成 hashCode()equals() 两者 . 在我们的案例中,我们使用了员工ID .

    • equals() 必须一致(如果未修改对象,则必须保持返回相同的值) .

    • 每当 a.equals(b) ,则 a.hashCode() 必须与 b.hashCode() 相同 .

    • 如果覆盖一个,则应覆盖另一个 .

    http://parameshk.blogspot.in/2014/10/examples-of-comparable-comporator.html

  • 1

    恕我直言,按照规则说 - 如果两个对象相等,那么它们应该具有相同的散列,即相等的对象应该产生相等的散列值 .

    如上所述,Object中的默认equals()是==,它对地址进行比较,hashCode()返回整数地址(实际地址上的散列),这对于不同的Object也是不同的 .

    如果需要在基于哈希的集合中使用自定义对象,则需要覆盖equals()和hashCode(),示例如果我想维护Employee对象的HashSet,如果我不使用更强的hashCode和equals我可能会重写覆盖两个不同的Employee对象,当我使用age作为hashCode()时会发生这种情况,但是我应该使用可以作为Employee ID的唯一值 .

  • 150

    为了帮助您检查重复的对象,我们需要一个自定义的equals和hashCode .

    由于哈希码始终返回一个数字,因此总是快速使用数字而不是字母键来检索对象 . How will it do? 假设我们通过传递一些其他对象中已有的值来创建一个新对象 . 现在,新对象将返回与另一个对象相同的哈希值,因为传递的值相同 . 一旦返回相同的哈希值,JVM将每次都转到相同的内存地址,如果同一个哈希值存在多个对象,它将使用equals()方法来识别正确的对象 .

  • 5

    如果要将自定义对象存储和检索为Map中的键,则应始终在自定义对象中覆盖equals和hashCode . 例如:

    Person p1 = new Person("A",23);
    Person p2 = new Person("A",23);
    HashMap map = new HashMap();
    map.put(p1,"value 1");
    map.put(p2,"value 2");
    

    这里p1和p2将仅视为一个对象,而 map 大小将仅为1,因为它们是相等的 .

  • 3
    public class Employee {
    
        private int empId;
        private String empName;
    
        public Employee(int empId, String empName) {
            super();
            this.empId = empId;
            this.empName = empName;
        }
    
        public int getEmpId() {
            return empId;
        }
    
        public void setEmpId(int empId) {
            this.empId = empId;
        }
    
        public String getEmpName() {
            return empName;
        }
    
        public void setEmpName(String empName) {
            this.empName = empName;
        }
    
        @Override
        public String toString() {
            return "Employee [empId=" + empId + ", empName=" + empName + "]";
        }
    
        @Override
        public int hashCode() {
            return empId + empName.hashCode();
        }
    
        @Override
        public boolean equals(Object obj) {
    
            if (this == obj) {
                return true;
            }
            if (!(this instanceof Employee)) {
                return false;
            }
            Employee emp = (Employee) obj;
            return this.getEmpId() == emp.getEmpId() && this.getEmpName().equals(emp.getEmpName());
        }
    
    }
    

    测试类

    public class Test {
    
        public static void main(String[] args) {
            Employee emp1 = new Employee(101,"Manash");
            Employee emp2 = new Employee(101,"Manash");
            Employee emp3 = new Employee(103,"Ranjan");
            System.out.println(emp1.hashCode());
            System.out.println(emp2.hashCode());
            System.out.println(emp1.equals(emp2));
            System.out.println(emp1.equals(emp3));
        }
    
    }
    

    在Object Class中,equals(Object obj)用于比较地址比较,这就是为什么在Test类中比较两个对象然后equals方法给出false但是当我们覆盖hashcode()时它可以比较内容并给出正确的结果 .

  • 5

    这两种方法都在Object类中定义 . 两者都是最简单的实现 . 因此,当您需要为这些方法添加更多实现时,您可以在类中进行覆盖 .

    对于Ex:对象中的equals()方法仅检查其在引用上的相等性 . 因此,如果您还需要比较其状态,那么您可以覆盖它,就像在String类中完成一样 .

  • 1

    Bah - "You must override hashCode() in every class that overrides equals()."

    [来自Effective Java,作者Joshua Bloch?]

    这不是错误的方式吗?覆盖hashCode可能意味着你正在编写一个哈希键类,但是重写equals肯定不会 . 有许多类不用作哈希键,但由于某些其他原因,确实需要逻辑相等测试方法 . 如果为它选择“等于”,那么您可能会被要求通过过度应用此规则来编写hashCode实现 . 所有这一切都是在代码库中添加未经测试的代码,这是一种等待将来绊倒某人的邪恶 . 编写您不需要的代码也是反敏捷的 . 这是错误的(并且生成的ide可能与您手工制作的equals不兼容) .

    当然他们应该在写入的对象上强制使用接口作为键吗?无论如何,Object应该永远不会提供默认的hashCode()和equals()imho . 它可能鼓励了许多破碎的哈希集合 .

    但无论如何,我认为“规则”被写回到前面 . 在此期间,我将继续避免使用“等于”等于平等测试方法:-(

相关问题