java学习-- equals和hashCode的关系

Posted 豁然高

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java学习-- equals和hashCode的关系相关的知识,希望对你有一定的参考价值。

hashcode的目的就是在hashset或者hashmap等中比较两个对象相等时,减少equals的使用次数来提高效率

 

以下为摘录

 

java中hashcode和equals的区别和联系

HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键

那么Java运行时环境是如何判断HashSet中相同对象、HashMap中相同键的呢?当存储了“相同的东西”之后Java运行时环境又将如何来维护呢? 


在研究这个问题之前,首先说明一下JDK对equals(Object obj)和hashcode()这两个方法的定义和规范:

 

 

在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。

equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false

hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。

 

接下来有两个个关于这两个方法的重要规范(我只是抽取了最重要的两个,其实不止两个):

规范1:

  若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该 相等”。

  不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。

规范2:

  如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。

 

根据这两个规范,可以得到如下推论:

1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。

2、如果两个对象不equals,他们的hashcode有可能相等。

3、如果两个对象hashcode相等,他们不一定equals。

4、如果两个对象hashcode不相等,他们一定不equals。 


Java运行时环境是怎样判断HashSet和HastMap中的两个对象相同或不同?

先判断hashcode是否相等,再判断是否equals。 

 

 

1. \'==\'是用来比较两个变量(基本类型和对象类型)的值是否相等的, 如果两个变量是基本类型的,那很容易,直接比较值就可以了。如果两个变量是对象类型的,那么它还是比较值,只是它比较的是这两个对象在栈中的引用(即地址)。 对象是放在堆中的,栈中存放的是对象的引用(地址)。由此可见\'==\'是对栈中的值进行比较的。如果要比较堆中对象的内容是否相同,那么就要重写equals方法了。

2. Object类中的equals方法就是用\'==\'来比较的,所以如果没有重写equals方法,equals和==是等价的。 通常我们会重写equals方法,让equals比较两个对象的内容,而不是比较对象的引用(地址)因为往往我们觉得比较对象的内容是否相同比比较对象的引用(地址)更有意义。

3. Object类中的hashCode是返回对象在内存中地址转换成的一个int值(可以就当做地址看)。所以如果没有重写hashCode方法,任何对象的hashCode都是不相等的。通常在集合类的时候需要重写hashCode方法和equals方法,因为如果需要给集合类(比如:HashSet)添加对象,那么在添加之前需要查看给集合里是否已经有了该对象,比较好的方式就是用hashCode。

4. 注意的是String、Integer、Boolean、Double等这些类都重写了equals和hashCode方法,这两个方法是根据对象的内容来比较和计算hashCode的。(详细可以查看jdk下的String.java源代码),所以只要对象的基本类型值相同,那么hashcode就一定相同。

5. equals()相等的两个对象,hashcode()一般是相等的,最好在重写equals()方法时,重写hashcode()方法; equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。 反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。在object类中,hashcode()方法是本地方法,返回的是对象的引用(地址值),而object类中的equals()方法比较的也是两个对象的引用(地址值),如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了。

 

有许多人学了很长时间的Java,但一直不明白hashCode方法的作用,我来解释一下吧。首先,想要明白hashCode的作用,你必须要先知道Java中的集合。  

总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。

你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。

那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。

但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。   

于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。

如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。  

这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。

这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。  

所以,Java对于eqauls方法和hashCode方法是这样规定的:

1、如果两个对象相同,那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同   

上面说的对象相同指的是用eqauls方法比较。    你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降

 

 

下面是一个重写equals和hashcode的例子:

public class Cat
{
    private String name;
    private String birthday;

    public Cat()
    {
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public void setBirthday(String birthday)
    {
        this.birthday = birthday;
    }

    public String getBirthday()
    {
        return birthday;
    }

    //重写equals方法 
    public boolean equals(Object other)
    {
        if(this == other)
        {
            //如果引用地址相同,即引用的是同一个对象,就返回true 
            return true;
        }

        //如果other不是Cat类的实例,返回false 
        if(!(other instanceOf Cat))
        {
            return false;
        }

        final Cat cat = (Cat)other;
        //name值不同,返回false 
        if(!getName().equals(cat.getName())
        return false;
        //birthday值不同,返回false 
        if(!getBirthday().equals(cat.getBirthday()))
            return false;

        return true;
    }

    //重写hashCode()方法   //重写hashcode时用31 做因子 ===  31 是2的5次方减一的素数;位移运算效率高,虚拟机对这方面的优化吧
    public int hashCode()
    {
        int result = getName().hashCode();
        result = 31 * result + getBirthday().hashCode();
        return result;
    }
} 

 

以上是关于java学习-- equals和hashCode的关系的主要内容,如果未能解决你的问题,请参考以下文章

Java开发进阶知识之学习篇——hashCode和equals

Java:Effective java学习笔记之 覆盖equals时总要覆盖hashcode

面试点:Java 中 hashCode() 和 equals() 的关系

从语言设计的角度探究Java中hashCode()和equals()的关系

Java hashCode与equals学习

java中equals与hashCode还有tostring方法学习记录