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

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

最近我读过这个开发人员工作文档。

本文档主要是关于有效和正确地定义hashCode()equals(),但是我无法理解为什么我们需要重写这两种方法。

我如何才能做出有效实施这些方法的决定?


Joshua Bloch谈有效Java

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

让我们试着用一个例子来理解如果我们在不覆盖hashCode()的情况下覆盖equals()并尝试使用Map会发生什么。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
    }

}

仅覆盖equals

如果只覆盖equals,那么当您调用myMap.put(first,someValue)时,首先将哈希到某个bucket,当您调用myMap.put(second,someOtherValue)时,它将哈希到另一个bucket(因为它们具有不同的hashCode)。所以,尽管它们是相等的,因为它们不会散列到同一个桶中,但地图无法实现,它们都留在地图中。

虽然如果我们重写hashCode(),不需要重写equals(),但是让我们看看在这种特殊情况下会发生什么,我们知道如果importantField相等,MyClass的两个对象相等,但我们不重写equals()

仅覆盖hashCode

想象你有这个

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

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

但问题是等号没有被重新定义,所以当地图对second进行散列,并通过桶迭代查看是否有对象k,这样second.equals(k)是真的,它就不会找到任何对象,因为second.equals(first)将是false

希望是清楚的


HashMapHashSet这样的集合使用对象的hashcode值来确定应该如何存储在集合中,然后再次使用hashcode来定位对象。在它的收藏中。

哈希检索是一个两步过程:

  • 找到合适的桶(使用hashCode())
  • 在桶中搜索正确的元素(使用equals())
  • 下面是一个很小的例子,说明为什么我们应该过度强调equals()hashCode()

    考虑一个Employee类,它有两个字段:Age和Name。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    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并测试该对象是否存在。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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());
        }
    }

    它将打印以下内容:

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

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

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

    现在可以看到为什么两个对象被认为是相等的,那么它们的哈希代码必须也是平等的?否则,您将永远无法找到对象,因为默认类对象中的hashcode方法实际上总是得到一个唯一的数字对于每个对象,即使equals()方法被重写为或者更多的物体被认为是相等的。如果他们的哈希代码没有反映出这一点。所以再来一次:如果两个物体相等,它们的哈希代码也必须相等。


    You must override hashCode() in every
    class that overrides equals(). Failure
    to do so will result in a violation of
    the general contract for
    Object.hashCode(), which will prevent
    your class from functioning properly
    in conjunction with all hash-based
    collections, including HashMap,
    HashSet, and Hashtable.

       from Effective Java, by Joshua Bloch

    通过一致地定义equals()hashCode(),可以提高类在基于哈希的集合中作为键的可用性。正如hashcode的api文档所解释的那样:"支持这种方法是为了让hashtables受益,比如java.util.Hashtable提供的hashtables。"

    关于如何有效地实现这些方法的问题,最好的答案是建议您阅读有效Java的第3章。


    简单地说,对象检查中的equals方法是否引用相等,当属性相等时,类的两个实例在语义上仍然相等。例如,将对象放入使用equals和hashcode的容器(如hashmap和set)时,这一点很重要。假设我们有一个类,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Foo {
        String id;
        String whatevs;

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

    我们创建两个具有相同ID的实例:

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

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

    • a.equals(b)为假,因为它们是两个不同的实例
    • a.equals(a)是真的,因为它是同一个实例
    • B.等于(B)是真的,因为它是同一个实例
    • 小精灵

      对的?好吧,也许,如果这是你想要的。但是,假设我们希望具有相同ID的对象是相同的对象,不管它是两个不同的实例。我们重写equals(和hashcode):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      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的helper方法


      身份不平等。

      • 等于运算符==测试标识。
      • equals(Object obj)方法比较等同性测试(即我们需要通过重写方法来判断等同性)
      • 小精灵

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

        首先,我们必须理解等号法的用法。

        为了识别两个对象之间的差异,我们需要重写Equals方法。

        例如:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        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

        现在hashcode方法很容易理解。

        hashcode生成整数,以便将对象存储在hashmap、hashset等数据结构中。

        假设我们有如上所述的Customer的override equals方法,

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

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

        • 不相等的实例可能具有相同的哈希代码。
        • 相同的实例应返回相同的哈希代码。
        • 小精灵


          好吧,让我用非常简单的词来解释这个概念。

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

          为了理解为什么我们必须重写equals和hashcode方法,如果需要首先了解什么是hashmap,什么是hashmap。

          hashmap是一种数据结构,它以数组的方式存储数据的键值对。假设一个[],其中"a"中的每个元素都是一个键值对。

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

          为什么要使用hashmap?如果我们必须在一个大数组中搜索,然后搜索每个数组,如果它们无效,那么散列技术告诉我们什么让我们用一些逻辑对数组进行预处理,并根据该逻辑(即散列)对元素进行分组

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

          现在除了上面的hashmap还告诉我们它不会在其中添加任何重复项。这就是我们必须重写equals和hashcode的主要原因

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

          所以hashmap有一个方法称为as-put(k,v),根据hashmap,它应该遵循上面的规则,有效地分布数组,不添加任何重复项。

          Put所做的是首先为给定的键生成哈希代码,以决定该值应该进入哪个索引。如果该索引中没有任何内容,则新值将添加到该索引中;如果该索引中已经存在某些内容,则新值应添加到该索引的链接列表末尾之后。但请记住,不应根据哈希图的所需行为添加重复项。假设你有两个整数对象,aa=11,bb=11。作为从对象类派生的每个对象,比较两个对象的默认实现是比较对象内的引用而不是值。因此,在上面的情况下,尽管语义相等,但都将无法通过相等性测试,并且可能存在两个具有相同哈希代码和相同值的对象,从而创建重复项。如果我们重写,就可以避免添加重复项。你也可以参考详细工作

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          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());


          }

          }


          hashCode()号:

          如果只重写哈希代码方法,则不会发生任何情况。因为它总是将每个对象作为对象类返回新的hashCode

          equals()号:

          如果只重写equal方法,那么a.equals(b)是真的,这意味着a和b的hashCode必须相同,但不会发生。因为您没有重写hashCode方法。

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

          因此,当需要在基于散列的集合中使用对象时,必须同时重写equals()hashCode()


          为了在诸如hashmap、hashtable等集合中使用我们自己的类对象作为键。我们应该通过了解集合的内部工作来重写这两个方法(hashcode()和equals())。否则,会导致错误的结果,而这是我们无法预料的。


          因为如果不重写它们,将在对象中使用默认的实现。

          考虑到这种情况,相等和hascode值通常需要知道组成对象的内容,因此它们通常需要在类中重新定义,以具有任何实际意义。


          添加到@lombo的答案中

          何时需要重写equals()?

          对象的equals()的默认实现是

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

          这意味着只有当两个对象具有相同的内存地址时,它们才会被认为是相等的,只有当您比较一个对象和它本身。

          但如果两个对象对一个对象具有相同的值,则可能需要将它们视为相同的对象。或其更多属性(请参阅@lombo's answer中给出的示例)。

          因此,在这些情况下,您将覆盖equals(),并为平等提供自己的条件。

          我已经成功地实现了equals(),它工作得很好。那么,为什么他们还要要求重写hashcode()?

          好吧。只要您不在用户定义的类上使用基于"哈希"的集合,就可以了。但在将来的某个时候,您可能希望使用HashMapHashSet,如果不使用override和"正确实现"hashcode(),这些基于哈希的集合将无法按预期工作。

          仅重写等于(添加到@lombo的答案中)

          1
          2
          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相同。只有当这些值相同时,才会继续检查同一存储桶中的相等性。

          但是这里的哈希代码对于这两个对象是不同的(因为它们与默认实现的内存地址不同)。因此,它甚至不关心检查是否平等。

          如果在重写的equals()方法中有一个断点,那么如果它们具有不同的哈希代码,它就不会介入。contains()检查hashCode()只有当它们相同时,它才会调用你的equals()方法。

          为什么我们不能让hashmap检查所有桶中的相等性呢?所以我没有必要重写hashcode()!!

          那么您就缺少了基于哈希的集合点。考虑以下内容:

          1
          Your hashCode() implementation : intObject%9.

          以下是以存储桶形式存储的键。

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

          比如说,你想知道地图上是否包含键10。你想搜索所有的桶吗?还是只搜索一个桶?

          根据散列代码,您可以确定如果存在10,那么它必须存在于bucket 1中。所以只搜索bucket 1!!


          1
          2
          3
          4
          5
          6
          7
          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()查找条目(bucket),然后equals()查找分录中的值
          • 小精灵

            如果两者都被覆盖,

            地图< A>

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

            如果不重写equals

            地图< A>

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

            如果未重写哈希代码

            地图< A>

            1
            2
            3
            4
            5
            6
            7
            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...

            哈希码等合约

          • 按相等方法相等的两个键应生成相同的哈希代码
          • 生成相同哈希代码的两个键不必相等(在上面的示例中,所有偶数都生成相同的哈希代码)

          • Java提出了一个规则

            "If two objects are equal using Object class equals method, then the hashcode method should give the same value for these two objects."

            因此,如果在我们的类中重写equals(),我们应该重写hashCode()方法,也遵循这个规则。例如,Hashtable中使用了两种方法(equals()hashCode()来将值存储为键值对。如果我们重写一个对象而不是另一个对象,那么如果我们将该对象用作键,那么Hashtable可能无法按我们想要的方式工作。


            1)常见错误见下例。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            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。哈希代码()引起的问题

            问题是由未重写的方法hashCode()引起的。equals()hashCode()之间的合同为:

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

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


          • 我在研究解释"如果您只重写hashcode,那么当您调用myMap.put(first,someValue)时,首先需要计算其hashcode并将其存储在给定的桶中。然后,当您调用myMap.put(first,someOtherValue)时,根据地图文档,它应该用第一个替换为第二个,因为它们是相等的(根据我们的定义)。

            我认为第二次添加myMap时,它应该是第二个对象,如myMap.put(second,someOtherValue)


            考虑在一个桶里收集所有黑色的球。你的工作是把这些球涂成如下颜色,然后用它来进行适当的比赛,

            网球-黄色,红色。板球-白色

            现在水桶里有三种颜色的球:黄色、红色和白色。现在你只需要知道哪种颜色适合哪种游戏。

            给球上色-散列。为比赛选择球-等于。

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


            Java中的等值和哈希代码方法

            它们是JavaLang.Objor类的方法,它是所有类的超级类(自定义类以及Java API中定义的其他类)。

            实施:

            public boolean equals(Object obj)

            public int hashCode()

            enter image description here

            公共布尔等于(对象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)返回真,y.equals(z)返回真,则x.equals(z)应返回真。

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

            For any non-null reference value x, x.equals(null) should return
            false.

            public int哈希代码()。

            此方法返回调用此方法的对象的哈希代码值。此方法以整数形式返回哈希代码值,并为基于哈希的集合类(如哈希表、哈希映射、哈希集等)提供支持。必须在重写Equals方法的每个类中重写此方法。

            hashcode的总合同是:

            每当Java应用程序执行过程中在同一对象上多次调用时,HASCODE方法必须始终返回相同的整数,如果没有修改对象中的相等信息。

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

            如果两个对象根据equals(object)方法相等,则对两个对象中的每个对象调用hashcode方法都必须产生相同的整数结果。

            如果两个对象不相等,就不需要按相等(Java)方法进行调用,那么在这两个对象的每一个上调用方法就必须产生不同的整数结果。但是,程序员应该意识到,为不同的对象生成不同的整数结果可能会提高哈希表的性能。

            Equal objects must produce the same hash code as long as they are
            equal, however unequal objects need not produce distinct hash codes.

            资源:

            爪哇

            图片


            它在使用值对象时很有用。以下是波特兰模式存储库的摘录:

            Examples of value objects are things
            like numbers, dates, monies and
            strings. Usually, they are small
            objects which are used quite widely.
            Their identity is based on their state
            rather than on their object identity.
            This way, you can have multiple copies
            of the same conceptual value object.

            So I can have multiple copies of an
            object that represents the date 16 Jan
            1998. Any of these copies will be equal to each other. For a small
            object such as this, it is often
            easier to create new ones and move
            them around rather than rely on a
            single object to represent the date.

            A value object should always override
            .equals() in Java (or = in Smalltalk).
            (Remember to override .hashCode() as
            well.)


            假设您的类(A)聚合了另外两个(B)(C),并且您需要将(A)的实例存储在哈希表中。默认实现只允许区分实例,但不允许通过(b)和(c)进行区分。所以A的两个实例可以相等,但默认情况下不允许您以正确的方式比较它们。


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


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

            下面我有一个简化的代码,它可以按个人提取人们的订单。Person正被用作哈希表中的键。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            42
            43
            44
            45
            46
            47
            48
            49
            50
            51
            52
            53
            54
            55
            56
            57
            58
            59
            60
            61
            62
            63
            64
            65
            66
            67
            68
            69
            70
            71
            72
            73
            74
            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);
                    }
                }
            }

            字符串类和包装类的equals()hashCode()方法的实现不同于对象类。对象类的equals()方法比较对象的引用,而不是内容。对象类的hashcode()方法为每个对象返回不同的hashcode,无论内容是否相同。

            当您使用映射集合时,它会导致问题,并且键是持久类型StringBuffer/Builder类型。因为它们不像字符串类那样重写equals()和hashcode(),所以当比较两个不同的对象时,equals()将返回false,即使两个对象的内容相同。它将使哈希映射存储相同的内容键。存储相同的内容键意味着它违反了映射规则,因为映射根本不允许重复的键。因此,您可以重写类中的equals()和hashcode()方法,并提供实现(IDE可以生成这些方法),以便它们与字符串的equals()和hashcode()工作相同,并阻止相同的内容键。

            您必须重写hashcode()方法和equals(),因为equals()根据hashcode工作。

            此外,将hash code()方法与equals()一起重写有助于保持equals()-hashcode()协定的完整性:"如果两个对象相等,则它们必须具有相同的哈希代码。"

            什么时候需要为hashcode()编写自定义实现?

            我们知道hashmap的内部工作是基于hashing的原则。有些桶存储入口。您可以根据您的需求定制hashcode()实现,以便将相同的类别对象存储到相同的索引中。使用put(k,v)方法将值存储到映射集合中时,put()的内部实现是:

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

            也就是说,它生成索引,并根据特定键对象的哈希代码生成索引。因此,让这个方法根据您的需求生成哈希代码,因为相同的哈希代码条目将存储在同一个bucket或索引中。

            就这样!


            imho,这是按照规则说的-如果两个对象相等,那么它们应该有相同的散列值,也就是说,相等的对象应该产生相同的散列值。

            在上面给出的情况下,对象中的默认equals()是==它对地址进行比较,hashcode()返回整数形式的地址(实际地址的hash),这对于distinct对象来说也是不同的。

            如果需要在基于哈希的集合中使用自定义对象,则需要同时重写equals()和hashcode(),例如,如果要维护Employee对象的哈希集,如果不使用更强大的hashcode和equals,则最终可能会重写这两个不同的Employee对象,但当我将age用作hashcode()时,会发生这种情况,但是我应使用唯一值,该值可以是员工ID。


            hashCode()方法用于获取给定对象的唯一整数。当这个对象需要存储在类似于HashTableHashMap的数据结构中时,这个整数用于确定存储桶的位置。默认情况下,对象的hashCode()方法返回对象所在内存地址的整数表示形式。

            当我们将对象插入到HashTableHashMapHashSet中时,使用对象的hashCode()方法。关于HashTables的更多信息,请参见wikipedia.org。

            要在地图数据结构中插入任何条目,我们需要键和值。如果键和值都是用户定义的数据类型,则键的hashCode()将决定在内部存储对象的位置。当需要从映射中查找对象时,该键的哈希代码将决定在何处搜索该对象。

            哈希代码只指向内部的某个"区域"(或列表、桶等)。由于不同的键对象可能具有相同的哈希代码,因此哈希代码本身不能保证找到正确的键。然后,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


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

            因为hashcode总是返回一个数字,所以使用数字而不是字母键检索对象总是很快。怎么办?假设我们通过传递一些在其他对象中已经可用的值来创建一个新对象。现在,新对象将返回与其他对象相同的哈希值,因为传递的值相同。一旦返回相同的散列值,JVM将每次转到相同的内存地址,如果同一散列值存在多个对象,它将使用equals()方法来标识正确的对象。


            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            42
            43
            44
            45
            46
            47
            48
            49
            50
            51
            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());
                }

            }

            测试类

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            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));
                }

            }

            在对象类中,equals(object obj)用于比较地址比较。这就是为什么在测试类中,如果比较两个对象,那么equals方法将给出false,但当重写hashcode()时,它可以比较内容并给出正确的结果。


            当您希望将自定义对象存储和检索为映射中的键时,您应该始终重写自定义对象中的equals和hashcode。如:

            1
            2
            3
            4
            5
            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,因为它们是相等的。


            这两种方法都是在对象类中定义的。两者都在其最简单的实现中。因此,当您需要向这些方法添加更多实现时,您的类中就有了重写。

            对于对象中的ex:equals()方法,只检查其在引用上的相等性。因此,如果您也需要比较它的状态,那么您可以像在字符串类中那样重写它。


            bah-"必须在重写equals()的每个类中重写hashcode()"

            [从有效的Java,由Joshua Bloch?]

            这不是绕错路吗?重写哈希代码可能意味着您正在编写哈希键类,但重写equals肯定不会。有许多类不用作哈希键,但出于某些其他原因,确实需要逻辑相等性测试方法。如果您为它选择"等于",那么您可能会被强制编写一个哈希代码实现,这是由于过度热心地应用了这个规则。所能做的就是在代码库中添加未经测试的代码,这是一种在未来等待绊倒某人的邪恶行为。另外,编写不需要的代码也是反敏捷的。这是错误的(并且一个IDE生成的可能与您手工制作的等价物不兼容)。

            当然,他们应该在编写的对象上指定一个接口作为密钥使用吗?无论如何,对象不应该提供默认的hashcode()和equals()imho。它可能会鼓励许多坏的哈希集合。

            但无论如何,我认为"规则"是写回前面的。同时,我将一直避免使用"equals"来表示相等测试方法:-(