Java中hashcode()和equals()方法

前言

在说hashcode()和equals()方法之前,我想先来说说Java中==与equals()方法的问题,==可用于比较基本数据类型(比较的是它们的数值是否相等),也可以用于比较对象在内存中的地址是否相等。

Java当中所有的类都继承与Object这个基类的,在Object中的基类中定义了一个equals()方法,这个方法的初始行为是比较对象的内存地址的(即判断两个对象是否为同一个对象),但是在一些类库中把这个方法重写了,比如我们最常见的String类型,它的equals方法比较的就是两个字符串的内存是否相等(源码我们下面再讨论)。还有一些包装类当中equals方法都有其自身的实现,而不再是比较对象在堆内存中存放的地址了。

因此,对于引用数据类型之间进行equals进行比较,在没有重写equals方法的情况下,它们比较的还是基于它们在内存中的存放的地址,因为Object的equals方法也是用==号进行比较的,所以比较后的结果和双等号==的结果相同。

下面来看看==与equals的一个测试例子

        EqualsTest test = new EqualsTest();
        EqualsTest test1 = new EqualsTest();

        System.out.println("equals比较结果: "+(test.equals(test1)));
        System.out.println("==比较结果: "+(test==test1));

得出的结果都为false。

把==与equals之间的区别理清楚了,我们再来看equals和hashcode方法之间的问题

equals方法

首先,在前言中已经提到,Object基类中,equals方法的初始行为是比较两个对象的内存地址的,可以看看它的源码

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

实际上,在该方法中,return的this==obj就反映了它比较的是两个对象的内存地址。但是我们知道, 有一些类库会把equals方法重写,那么就要根据具体的代码来确定equals方法的作用了,覆盖后一般都是通过对象的内容是否相等来判断对象是否相等。我们以最常见的String类型为例,先看看它的String类中的equals方法的源码

public boolean equals(Object anObject) {  
    if (this == anObject) {  
        return true;  
    }  
    if (anObject instanceof String) {  
        String anotherString = (String)anObject;  
        int n = count;  
        if (n == anotherString.count) {  
        char v1[] = value;  
        char v2[] = anotherString.value;  
        int i = offset;  
        int j = anotherString.offset;  
        while (n-- != 0) {  
            if (v1[i++] != v2[j++])  
            return false;  
        }  
        return true;  
        }  
    }  
    return false;  
    }

我简单地说一下其中的步骤:a.equals(b)的操作中,包含以下步骤:

1.首先判断a==b,即判断a b是否为同一个对象,如果是,则直接返回true,否则进行第2步

2.判断对比对象是否是String类型,是则继续第3步,否则返回false

3.判断a b两个字符串的长度是否一样,是则进行第4步,否则返回false

4.逐个比较a  b两个字符串中的每个字符,是则返回true,否则返回false

当然,equals方法还有一些良好的特性,比如自反性、对称性、传递性、一致性、非空性,这些我就略过,这些和我们数学中对应特性相似。

hashcode方法

hashcode方法是从Object类继承过来的,Object类中hashcode方法是返回对象在内存中的地址转换成的int值,如果对象没有重写hashcode方法,任何对象的hashcode方法的返回值都是不相等的。hashCode()方法返回的就是一个数值,从方法的名称上就可以看出,其目的是生成一个hash码。hash码的主要用途就是在对对象进行散列的时候确定对象的存储地址的,据此很容易推断出,我们需要每个对象的hash码尽可能不同,这样才能保证散列的存取性能。事实上,Object类提供的默认实现确实保证每个对象的hash码不同(在对象的内存地址基础上经过特定算法返回一个hash码)。Java采用了哈希表的原理。 哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址。

我们来看看hashcode的定义

public native int hashCode();

我们发现在定义的时候有native修饰符,说明是一个本地方法,它的实现是根据本地机器相关的。也就是说这个方法可以理解为返回对象的物理地址。

其实,咱Java中,hashcode方法的应用比较经典的是用在基于散列的结合,也就是我们常说的集合当中带hash单词的。比如HashSet、HashMap、Hashtable,hashcode方法是用来在散列结构确定对象的存储地址的。

在上述的这么散列集合当中,比如HashSet是不允许元素重复的,那么HashSet在存储的时候,如何判断两个元素重复呢?我们首先想到的是equlas方法。但是,如果这些散列集合中每增加一个元素就调用一次equals方法,那么集合中的元素很多时,每新增一个元素时,就需要调用很多次equals方法,这显然会大大降低性能。

于是,Java采用了哈希的方法来避免equals方法的过于频繁地调用,它是这么做的:当向集合中新增一个对象时,先调用这个对象的hashcode方法,得到该对象的hashcode值(即得到对象存储的物理地址),如果该位置已经有值了,就再调用equals方法与新元素进行比较,如果两者相同,就代表两者是同一个对象,就不做存储;如果不同,则再散列到其他的地址进行存储。

在Java中,hashcode方法和equals方法有相关的规则:

1.相等(相同)的对象必须具有相等的哈希码(或者散列码)。

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

关于第一点,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相同的元素是不允许同时存入HashMap,因为HashMap不允许存放重复元素。

关于第2点,也就是说,不同对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。

所以在Java中,如果两个对象的equals方法返回true,那么两个对象的hashcode方法返回也必然是true,如果两个对象的equals方法返回的为false,那么两个对象的hashcode方法返回的有可能是true,也有可能为false。如果两个对象的hashcode方法返回为true,那么两个对象的equals方法返回的有可能是true,也有可能为false;如果两个对象的hashcode方法返回值不同,则equals方法返回的值必然为false

hashcode方法应该如何写以及equals方法如何重写呢?

如果我们自己来写自定义的hashcode方法以及实现equals方法的重写应该如何来写呢?

我想我们应该遵守以下规则:

1.尽量保证使用对象的同一属性来生成hashcode()和equals()两个方法

2.在重写equals方法的同时,必须重写hashcode方法(两者必须同时重写)


猜你喜欢

转载自blog.csdn.net/kuangsonghan/article/details/80318792