为什么需要重写Java中的equals和hashCode方法?


383

最近,我通读了这份 Developer Works文档

该文档主要是关于定义hashCode()以及equals()有效和正确的内容,但是我无法弄清为什么我们需要覆盖这两种方法。

我该如何决定有效地实施这些方法?


4
在programming.guide上有两篇很棒的文章准确地解释了这一点:什么时候应该覆盖equals?以及为什么在覆盖equals时应该始终覆盖hashCode。(警告,接受的答案实际上是错误的。)
aioobe

大小写替代仅等于:两个相同的对象将具有不同的哈希码=相同的对象位于不同的存储桶(重复)中。仅覆盖案例哈希码:两个相同的对象将具有相同的哈希码=相同的对象进入相同的存储桶(重复)。
VdeX

Answers:


524

约书亚·布洛赫(Joshua Bloch)谈有效Java

您必须在每个覆盖equals()的类中覆盖hashCode()。否则将导致违反Object.hashCode()的常规协定,这将阻止您的类与所有基于哈希的集合(包括HashMap,HashSet和Hashtable)一起正常运行。

让我们尝试通过一个示例来理解它,如果我们在equals()不进行覆盖的情况下进行覆盖hashCode()而尝试使用Map

假设我们有一个类像这样那样的两个对象MyClass是相等的,如果他们importantField等于(与hashCode()equals()Eclipse生成)

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)第一次调用时将哈希到某个存储桶,而在调用myMap.put(second,someOtherValue)时将哈希到其他存储桶(因为它们具有不同的hashCode)。因此,尽管它们相等,但由于它们没有散列到同一个存储桶中,因此地图无法实现,因此它们都留在了地图中。


尽管不必重写equals()就可以覆盖hashCode(),但是让我们看看在这种特殊情况下会发生什么,在这种情况下,我们知道的两个对象MyClass相等就可以了importantField,但是我们不覆盖equals()

仅覆盖 hashCode

想象你有这个

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

希望很清楚


5
在第二种情况下,您能否再详细说明一下,为什么第二个对象必须放在另一个存储桶中?
侯赛因·阿赫塔尔·瓦希德

57
我不喜欢这个答案,因为它表明您不能在不覆盖equals()的情况下覆盖hashCode(),这简直是不正确的。您说您的示例代码(“仅覆盖hashCode”部分)将无法工作,因为您两个对象定义为相等,但是-抱歉-此定义仅在您的脑海中。在第一个示例中,您有两个具有相同hashCode的不相等对象,这完全合法。因此,您需要覆盖equals()的原因不是因为您已经覆盖了hashCode(),而是因为您想将“ equals”定义从头转移到代码中。
user2543253 2014年

11
if you think you need to override one, then you need to override both of them是错的。hashCode如果您的类重写equals但reverse不正确,则需要重写。
akhil_mittal

4
我认为只覆盖hashCode()而不覆盖equals()是完全可以的。这也是用有效的Java语言编写的内容:books.google.fr/…–
约翰尼,

2
@PhantomReference,请注意,只有重写equals才能违反javadoc中阐明的约定Object“如果根据equals(Object)方法两个对象相等,则hashCode在两个对象中的每个对象上调用方法必须产生相同的整数结果。” 当然,并非所有合同的所有部分都在所有代码中得到行使,但从形式上来讲,这仍然是违规行为,我认为这是一个等待发生的错误。
aioobe '16

263

集合(例如HashMapHashSet使用对象的哈希码值)确定应如何将其存储在集合中,以及哈希码再次使用以便在对象的集合中定位该对象。

散列检索分为两个步骤:

  1. 找到合适的铲斗(使用 hashCode()
  2. 在存储桶中搜索正确的元素(使用equals()

这是一个小例子,说明为什么我们应该压倒一切equals()hashcode()

考虑一个Employee具有两个字段的类:年龄和名称。

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()方法被覆盖为将两个或更多对象视为相等也是如此。如果对象的哈希码不反映对象的相等程度,则无所谓。所以再来一次:如果两个对象相等,则它们的 哈希码也必须相等。


4
完美的例子。清楚地表明了差异!
coderpc '17

3
很好的解释@rajeev
VdeX

2
@VikasVerma等于对象将具有相等的哈希码,并不意味着不相等的对象将具有不相等的哈希码。如果对象实际上不同,但其哈希码相同,该怎么办?
拉维

1
解释得非常好:)
Rahul

4
更好的答案,然后接受的答案!谢谢
浮木

50

您必须在每个覆盖equals()的类中覆盖hashCode()。否则将导致违反Object.hashCode()的常规协定,这将阻止您的类与所有基于哈希的集合(包括HashMap,HashSet和Hashtable)一起正常运行。


Joshua Bloch撰写的    来自Effective Java的文章

通过定义equals()hashCode()保持一致,可以提高类作为基于哈希的集合中的键的可用性。正如hashCode的API文档所解释的那样:“支持此方法是为了使哈希表(例如由提供的哈希表)受益java.util.Hashtable。”

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


4
这是正确的答案。当然,必然的结果是,如果您从不在基于哈希的集合中使用该类,则没有实现也没关系hashCode()
2015年

1
在更复杂的情况下,您永远都不知道所使用的集合是否在使用哈希,因此请远离“没关系,您还没有实现hashCode()”
Victor Sergienko 2016年

1
我可以覆盖hashCode()而不覆盖equals()吗?
约翰尼

@StasS,是的,与接受的答案相反。请参阅本文第二部分中的解释:为什么覆盖相等时总是要覆盖hashCode
aioobe

22

简而言之,Object中的equals-method会检查引用是否相等,因为当属性相等时,类的两个实例在语义上仍然相等。例如,在将对象放入使用等于和哈希码的容器(例如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的对象成为同一对象,而不管它是两个不同的实例。我们覆盖等号(和哈希码):

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

至于实现等于和哈希码,我可以建议使用Guava的辅助方法


20

身份不是平等。

  • 等于操作员==测试身份。
  • equals(Object obj) 方法比较相等性测试(即,我们需要通过覆盖方法来告诉相等性)

为什么需要重写Java中的equals和hashCode方法?

首先,我们必须了解equals方法的使用。

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

例如:

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生成整数以便将对象存储在HashMapHashSet之类的数据结构中。

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

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

在将数据存储在存储桶中时使用数据结构(存储桶是文件夹的漂亮名称)。如果我们使用内置的哈希技术,则对于上述两个客户,它将生成两个不同的哈希码。因此,我们将相同的对象存储在两个不同的位置。为避免此类问题,我们还应根据以下原则重写hashCode方法。

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

3
这是我最近1个小时以来一直在寻找的东西。令人敬畏的同伴(y)
阿德南

13

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

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

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

哈希图是一种数据结构,它以数组形式存储数据的键值对。假设a [],其中“ a”中的每个元素都是一个键值对。

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

现在为什么要使用哈希图?如果我们必须在一个大型数组中搜索,然后搜索每个数组是否效率不高,那么什么哈希技术告诉我们,可以使用某种逻辑对数组进行预处理,并根据该逻辑对元素进行分组,即哈希

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

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

因此,当其说明哈希表的内部工作时,我们需要查找哈希表有哪些方法,以及哈希表如何遵循上述我所解释的规则

因此,哈希图具有称为put(K,V)的方法,根据哈希图,哈希图应遵循上述有效分配数组且不添加任何重复项的规则

因此,put的工作是首先为给定的键生成哈希码,以决定该值应该进入哪个索引。如果该索引处不存在任何索引,则将在该索引处添加新值(如果该索引已经存在)然后应在链接列表的末尾在该索引处添加新值。但请记住,根据哈希图的预期行为,不应添加任何重复项。假设您有两个Integer对象aa = 11,bb = 11。与从对象类派生的每个对象一样,比较两个对象的默认实现是比较引用而不是对象内部的值。因此,在上述情况下,尽管在语义上相等,但都将通过相等性测试,并且可能存在具有相同哈希码和相同值的两个对象,从而产生重复项。如果覆盖,则可以避免添加重复项。您也可以参考详细工作

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


}

}

我有一个困惑,为什么在HashMap的情况下重写hashCode方法时,为什么需要重写equals方法?无论如何,如果对象的哈希码相等,则hashmap将替换该值。
维卡斯·维尔玛

如果对象的哈希码相等,则@VikasVerma哈希图不会替换任何类型的值,它仅确定必须将新添加的对象放置在哈希图中的索引。现在在索引处可以有对象,因此为了避免重复,我们重写了equals方法,并编写了用于定义何时将两个比较对象视为相等的逻辑。如果不被覆盖,则将存储具有相同值的对象,因为这两个对象的引用将不同
Chetan

11

hashCode()

如果仅覆盖哈希码方法,将不会发生任何事情。因为它总是将hashCode每个对象作为Object类返回new 。

equals()

如果仅覆盖equal方法,a.equals(b)则为true表示hashCodea和b的a必须相同但不会发生。因为您没有覆盖hashCode方法。

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

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


关于仅覆盖hashCode(),这很有趣。很好,对吧?还是有问题的情况?
约翰尼

1
这是一个误导和错误的答案。重写(= only =)hashCode()确保具有相似属性的各个类的实例化的每个对象都具有相同的哈希码。但是不会有用,因为它们都不相等。
mfaisalhyder

8

Java制定了一条规则

“如果使用Object类equals方法使两个对象相等,则hashcode方法应为这两个对象提供相同的值。”

因此,如果在我们的类中重写,则我们equals()也应该重写hashcode()方法以遵循此规则。例如,equals()和中的两种方法hashcode()都用于Hashtable将值存储为键值对。如果我们覆盖一个而不是另一个,则Hashtable如果我们使用这样的对象作为键,则可能无法按我们期望的那样工作。


6

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

鉴于实例相等性和hascode值通常需要了解组成对象的知识,因此通常需要在类中对其进行重新定义以具有任何实际含义。


6

为了将我们自己的类对象用作HashMap,Hashtable等集合中的键,我们应该通过了解集合的内部工作来覆盖这两种方法(hashCode()和equals())。否则,将导致错误的结果,这是我们无法预期的。


6

添加到@Lombo的答案

您何时需要覆盖equals()?

Object的equals()的默认实现是

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

这意味着两个对象只有在它们具有相同的内存地址时才被认为是相等的;只有当您将一个对象与其自身进行比较时,这才是正确的。

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

因此,equals()在这些情况下您将优先考虑,并给出自己的平等条件。

我已经成功实现了equals(),并且运行良好,为什么他们还要覆盖hashCode()?

好。只要您在用户定义的类上不使用基于“哈希”的Collections,就可以了。但是在将来的某个时候,您可能想使用,HashMap或者HashSet如果您不这样做override“正确实现” hashCode(),那么这些基于哈希的集合将无法按预期工作。

覆盖仅等于(@Lombo的答案的补充)

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

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

但是这里的hashCode对于这两个对象是不同的(因为它们具有与默认实现不同的内存地址)。因此,它甚至都不关心检查是否相等。

如果在覆盖的equals()方法中有一个断点,则如果它们具有不同的hashCodes,则不会介入。 contains()检查hashCode()并且仅当它们相同时才调用您的equals()方法。

为什么我们不能使HashMap在所有存储桶中检查是否相等?因此,我没有必要覆盖hashCode()!

然后,您将失去基于散列的集合的意义。考虑以下 :

Your hashCode() implementation : intObject%9.

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

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

假设您想知道地图上是否包含键10。是否要搜索所有存储桶?还是只想搜索一个桶?

根据hashCode,您将确定如果存在10,则必须在存储桶1中存在它。因此,将仅搜索存储桶1!


5
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()在Entry中查找值

如果两者都被覆盖,

地图< >

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

如果不等于

地图< >

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.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平等合同

  1. 根据相等方法相等的两个键应生成相同的hashCode
  2. 生成相同hashCode的两个键不必相等(在上面的示例中,所有偶数生成相同的hash Code)

4

考虑将所有球收集在黑色的水桶中。您的工作是按照以下步骤为这些球着色,并将其用于适当的比赛中,

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

现在,桶中有黄色,红色和白色三种颜色的球。现在,您进行了着色只有您知道哪种颜色适合哪种游戏。

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

如果您进行了着色,并且有人选择板球或网球球,他们将不会介意颜色!!!


4

我正在研究“如果仅重写hashCode,则在调用myMap.put(first,someValue)它时首先需要计算它的hashCode并将其存储在给定存储桶中。然后在调用时myMap.put(first,someOtherValue)时应按照地图文档的说明先用第二个替换,因为它们相等(根据我们的定义)。” :

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


4

1)常见错误如下例所示。

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()引起的问题

该问题是由不可覆盖的方法引起的hashCode()equals()和之间的合同hashCode()是:

  1. 如果两个对象相等,则它们必须具有相同的哈希码。
  2. 如果两个对象具有相同的哈希码,则它们可以相等或可以不相等。

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

4

在使用Value Objects时很有用。以下是波特兰模式存储库的摘录:

价值对象的示例包括数字,日期,金额和字符串之类的东西。通常,它们是被广泛使用的小物体。他们的身份是基于他们的状态,而不是他们的对象身份。这样,您可以具有相同概念值对象的多个副本。

因此,我可以有一个代表1998年1月16日这个日期的对象的多个副本。这些副本中的任何一个都是相等的。对于这样的小对象,创建新对象并四处移动通常要容易得多,而不是依靠单个对象来表示日期。

在Java中,值对象应始终覆盖.equals()(在Smalltalk中为=)。(请记住也要覆盖.hashCode()。)


3

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


3

equals和hashcode方法在对象类中定义。默认情况下,如果equals方法返回true,则系统将继续检查哈希码的值。如果两个对象的哈希码也相同,则这些对象将被视为相同。因此,如果覆盖仅equals方法,则即使覆盖的equals方法指示2个对象相等,系统定义的哈希码也可能不会指示2个对象相等。因此,我们还需要覆盖哈希码。


如果equals方法返回true,则无需检查哈希码。但是,如果两个对象具有不同的哈希码,则无需调用equals就可以将它们视为不同的哈希码。此外,知道列表上的所有事物都没有特定的哈希码意味着,列表上的所有事物都无法将任何对象与该哈希码进行匹配。举一个简单的例子,如果一个具有哈希码为偶数的对象的列表,以及一个哈希码为奇数的对象的列表,那么哈希码为偶数的对象将不在第二个列表中。
2013年

如果一个对象有两个对象X和Y,它们的“相等”方法表明它们匹配,但是X的哈希码是偶数,Y的哈希码是奇数,则如上所述的集合表明对象Y的哈希码是奇数并存储它会在第二个列表上找不到对象X的匹配项。它将观察到X的哈希码是偶数,并且由于第二个列表中没有任何对象带有偶数的哈希码,因此它不会打扰在那里搜索与X匹配的东西,即使Y与X匹配。您应该说...
超级猫

...将是许多集合将避免比较其哈希码暗示它们不能相等的事物。给定两个对象的哈希码未知,通常直接比较它们比计算它们的哈希码更快,因此无法保证报告不相等哈希码但返回true的东西equals将不被视为匹配。另一方面,如果发生收集,请注意事物不能具有相同的哈希码,它们很可能不会注意到它们是相等的。
2013年

3

Java中的Equals和Hashcode方法

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

实现方式:

公共布尔等于(Object obj)

public int hashCode()

在此处输入图片说明

公共布尔等于(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,只要未修改对象的equals比较中使用的任何信息,对x.equals(y)的多次调用将始终返回true或始终返回false。

对于任何非null参考值x,x.equals(null)应该返回false。

public int hashCode()

此方法返回在其上调用此方法的对象的哈希码值。此方法以整数形式返回哈希码值,并且支持基于哈希的收集类(例如Hashtable,HashMap,HashSet等)。此方法必须在覆盖equals方法的每个类中被覆盖。

hashCode的一般约定为:

在Java应用程序的执行过程中,只要在同一对象上多次调用它,则hashCode方法必须一致地返回相同的整数,前提是未修改该对象的equals比较中使用的信息。

从一个应用程序的执行到同一应用程序的另一执行,此整数不必保持一致。

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

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

只要相等,相等的对象就必须产生相同的哈希码,但是不相等的对象不必产生不同的哈希码。

资源:

Java牧场

图片


图片(视频链接)处于私人模式。公开观看。
UdayKiran Pulipati

2

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

我下面的内容是一个简化的代码,可以按人员列出人们的命令。人员被用作哈希表中的键。

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

2

字符串类和包装器类对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()契约:“如果两个对象相等,则它们必须具有相同的哈希码。”

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

众所周知,HashMap的内部工作是基于散列原理的。在某些存储桶中存储条目集。您可以根据需要自定义hashCode()实现,以便可以将相同的类别对象存储到相同的索引中。当您使用put(k,v)方法将值存储到Map集合中时,put()的内部实现是:

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

就是说,它生成索引,并且索引是基于特定键对象的哈希码生成的。因此,使此方法根据您的要求生成哈希码,因为相同的哈希码条目集将存储在相同的存储桶或索引中。

而已!


1

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

hashCode()当我们将它们插入到一个对象的方法使用HashTableHashMapHashSet。有关HashTablesWikipedia.org的更多信息,以供参考。

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

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

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

笔记:

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

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

  3. 无论何时a.equals(b),则a.hashCode()必须与b.hashCode()

  4. 如果覆盖一个,则应覆盖另一个。

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


hashCode()不用于为每个对象返回唯一的整数。那是不可能的。您自己在第四段的第二句中与此矛盾。
洛恩侯爵,

@EJP,大多数时候hascode()将返回两个不同对象的唯一整数。但是有可能会碰撞两个不同对象的hascode,这个概念称为Hashcode Collision。请参考:tech.queryhome.com/96931/...
Paramesh Korrakuti

1

恕我直言,这是按规则说的-如果两个对象相等,则它们应具有相同的哈希值,即相等的对象应产生相等的哈希值。

上面给出,对象中的默认equals()==,它对地址进行比较,hashCode()返回整数形式的地址(实际地址上的哈希值),这对于不同的Object也是不同的。

如果需要在基于Hash的集合中使用自定义对象,则需要覆盖equals()和hashCode(),例如如果我想维护Employee Objects的HashSet,如果我不使用更强大的hashCode和equals我可能最终会覆盖两个不同的Employee对象,这在我将年龄用作hashCode()时发生,但是我应该使用唯一的值,它可以是Employee ID。


1

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

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


1

当您要在Map中存储和检索自定义对象作为键时,则应始终在自定义Object中覆盖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,因为它们相等。


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

}

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


我在下面的程序中添加了Test类。
Manash Ranjan Dakua

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

1
您可以使用编辑链接只是这个答案下面添加到您的答案。请不要添加一个答案,两个不完整的人
苏拉杰拉奥

1

如果不进行覆盖equals()hashcode()除非您或其他人在诸如的哈希集合中使用该类类型,否则您将不会发现任何问题HashSet。在我之前的人们已经清楚地多次解释了文献记载的理论,我只是在这里提供一个非常简单的例子。

考虑一个班级,其equals()需要表示定制的内容:-

    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

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

    }

现在考虑这个主要类别:

    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

这将产生以下输出:-

    true
    -----------------------------------
    true
    -----------------------------------
    1

我对结果感到满意。但是,如果没有覆盖hashCode(),它将导致噩梦,因为Rishav具有相同成员内容的对象将不再被视为唯一对象hashCode,因为默认情况下生成的对象将有所不同,这将是输出:-

    true
    -----------------------------------
    false
    -----------------------------------
    2

0

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

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


-3

Bah-“您必须在每个覆盖equals()的类中覆盖hashCode()。”

[摘自Joshua Bloch的《有效Java》?

这不是错误的方法吗?覆盖hashCode可能意味着您正在编写哈希键类,但是覆盖等号肯定不是。有许多类没有用作哈希键,但出于某些其他原因,确实希望使用逻辑相等性测试方法。如果为它选择“等于”,则可能由于过分热衷于此规则而被授权编写hashCode实现。所要做的就是在代码库中添加未经测试的代码,这是邪恶的等待,将来使某人绊倒。同样,编写不需要的代码也是抗敏捷的。这是错误的(想法产生的想法可能与您手工制作的等式不兼容)。

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

但是无论如何,我认为“规则”是写在前面的。同时,我将继续避免在相等性测试方法中使用“等于” :-(

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.