首页 > 代码库 > 学习笔记------- ==   equals   hashcode

学习笔记------- ==   equals   hashcode

先来回顾一下 == equals 的区别

==是运算符

java中的数据类型,可分为两类:

1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean ,他们之间的比较,应该用双等号(==,比较的是他们的值。

2.引用数据类型,比如字符串,或者一个类的对象   当他们用(==)进行比较的时候,实际上比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false

equals是方法

JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址的(等同于“==”

Object类中:

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

但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,比如String类中

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

因此,此时的equals不再是比较类在堆内存中的存放地址了。

对于引用类型数据之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的。

测试如下:

技术分享

那么hashcode()是什么?

equals(Object obj)一样,在Java中任何一个对象都具备hashcode()这个方法,因为他也是在Object类中定义的。 不同的是equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false

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

关于这两个方法有一些必须要遵循的规范(最重要的两个)

规范1:若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该 相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回truehashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG

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

总结如下:

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

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

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

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

 

上面我们已经知道String类重写了equals方法,那它有没有重写hashcode方法呢?

Object中的hashcode方法:

public native int hashCode();

对,你没有看错object中并没有实现hashcode  他是一个native方法,只在联合开发中用到,我们只用知道底层会返回给我们对应的int值即可。

而在String类中

public int hashCode() {
       int h = hash;
       if (h == 0 && value.length > 0) {
           char val[] = value;
 
           for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
           }
           hash = h;
       }
       return h;
}

可以看到String已经将hashcode重写。测试一下

技术分享

当然hashcode相当 不一定equals

技术分享

从上可知,不同的对象,hashcode()可能相等的,这也就有了hash表的冲突解决问题,有了对象相等比较时的equals()问题了。

 

重写equals方法后为什么要重写hashcode方法,遵从这个规范会给我们带来哪些好处?

重写equals方法时需要重写hashCode方法,主要是针对MapSet等集合类型的使用

我们先来了解一下hashcode的作用。

HashCode有什么用?不妨举个例子:

1、假设内存中有0 1 2 3 4 5 6 7 88个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找

2、使用HashCode则效率会快很多,把IDHashCode%8,然后ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过IDHashCode%8求余数直接找到存放的位置了

3、如果IDHashCode%8算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。

4、如果IDHashCode%8相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的。

举个实际的例子Set。我们知道Set里面的元素是不可以重复的,那么如何做到?Set是根据equals()方法来判断两个元素是否相等的。比方说Set里面已经有1000个元素了,那么第1001个元素进来的时候,最多可能调用1000equals方法,如果equals方法写得复杂,对比的东西特别多,那么效率会大大降低。使用HashCode就不一样了,比方说HashSet,底层是基于HashMap实现的,先通过HashCode取一个模,这样一下子就固定到某个位置了,如果这个位置上没有元素,那么就可以肯定HashSet中必定没有和新添加的元素equals的元素,就可以直接存放了,都不需要比较;如果这个位置上有元素了,逐一比较,比较的时候先比较HashCodeHashCode都不同接下去都不用比了,肯定不一样,HashCode相等,再equals比较,没有相同的元素就存,有相同的元素就不存。如果原来的Set里面有相同的元素,只要HashCode的生成方式定义得好(不重复),不管Set里面原来有多少元素,只需要执行一次的equals就可以了。这样一来,实际调用equals方法的次数大大降低,提高了效率。

简单来说,一般用java中的Map对象进行存储时,他会先自动调用hashCode方法来比较两个对象是否相等,如果相等再继续根据equals方法判断是否为同一对象。

所以如果我们对equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。

举个hashset的小例子:

技术分享运行结果:

第一部分是new出来的两个对象,它全加了进去。

第二部分,对象是相同的只加一个。

第三部分是new出来的两个对象,但String重写了hashcodeequals方法,断定为相同。

第一部分中,Persion类是Object的子类继承了Objectequalshashcode方法,而在Object类当中它的不同的实例中的hashcode是不同的,这是Object中本来就定义好了的hashcode;所以继承它的类也都有这样的方法,所以第一部分通过equals之后Persion的两个实例是不相同的

 

第一部分钟如果重写equals方法,不重写hashcode方法有什么结果呢?

加入我们定义名字相同的人就是同一个人,然后重写equals方法,不重写hashcode

public boolean equals(Object obj) {
       if(this==obj)
       {
           return true;
       }
       if(obj!=null&&obj instanceof Person)
       {
           Person p = (Person)obj;
           if(name.equals(p.name))
           {
                return true;
           }
       }
       return false;
}

测试

技术分享

定义好名字相同即为一人,在加入到set里时却还是认为是两个不同的人。

所以重写equals方法时也要重写hashcode方法。



学习笔记------- ==   equals   hashcode