关于HashCode和equals的理解

   在Java中,hashCode()和equals()是基类Objcet的两个方法,它们在Object中的实现十分简单,如下:hashCode函数主要功能是返回一个Object对象的物理地址,equals则比较两个Object的引用地址是否相等。

        而在实际应用中,那些继承自Object往往需要根据实际情况重写equals,例如String类重写了equals方法,使得两个String类型的对象相等的条件变成了对象内容相同,而不是简单的引用的地址相等,这是具有实际意义的。

在讨论这个问题之前,我们还有需要了解一点预备知识,我们就经常看到一句所谓的名言“两个对象equals,hashcode一定相等;两个对象hashcode相等,但是不一定equals”。举个例子就明白了,String类重写equals了,两个相等的条件是逐个比较两个字符串,如果两个字符串每一个字符都相等,则equals。而hashcode函数则是实现了一个复杂的hash函数(根据字符串的长度而定的一个多项式函数),可以这么类比,equals是忠实的比较两个对象,而hashcode则是使用一个hash函数,将两个对象作为输入条件,而最终比较的是hash函数的输出结果。众所周知,一个好的hash函数会使得不同的输入有不同的输出,但是也无法避免的可能存在不同的输入对应相同的输出,这也被称为Hash冲突。另外,作为一个函数,相同的输入是不可能得到不同的输出的,反过来说,如果两个对象经过hash函数运算输出不同,则它们必然输入(即对象本身)不相等(因为这俩是逆否命题)。

上面关于重写equals和hashcode方法真正派上用场的地方在于集合类的使用。集合类中,要求对象是不能有相同的元素,这和代数中的要求是一样的,因此每次向集合对象中添加元素时,会首先判断集合中是否已经包含了该对象,它是这样判断的:

条件1.先比较两个对象的hashcode,如果等则进入条件2比较。如果不等直接则返回false。(具体参见HashMap.get()方法的源码)

条件2.比较equals。

于是存在这么一种情况,如果HashSet对象中的元素是Student对象,定义如下:

Public class Student{

String name;

int age;

}

       这时,我们在向HashSet对象插入Student对象时,HashSet对象会将这个student对象和集合中已经存在的所有student对象逐个比较。方法如前所述,如果student不重写equals方法,则使用继承自Object的equals方法,显然这是不符合情景的,因此重写equals方法:

@Override

publicboolean equals(Object obj) {

Student stu = (Student)obj;

if(this.getName().equals(stu.getName()) && this.getAge() == stu.getAge()){

returntrue;

}else{

returnfalse;

}

}

此时测试代码:

Student stu1 = new Student();

Student stu2 = new Student();

stu1.setName("Ann");

stu1.setAge(2);

stu2.setName("Ann");

stu2.setAge(2);

System.out.println("stu1.equals(stu2):" + stu1.equals(stu2));

输出结果为:stu1.equals(stu2):false。未复写当然为false.

此时复写hashcode方法,使得内容相同的Student对象具有相同的hashcode值,当然为了测试集合类中contain函数是否如前所说是先比较对象的hashcode值,再比较equals函数,可以改写hashcode函数使得它返回相同的值,此时可以通过改变equals复写方式,从而看出contain比较顺序确如之前所说。

@Override

publicint hashCode() {

returnthis.name.hashCode() + age;

}

       因此在hashset这个例子当中,由于首先比较两个对象的hashcode,再比较equals,因此,如果不重写hashcode函数的,即使两个对象的内容相等,那么由于他们的hashcode不等,这样他也会被加入到Hashset当中,这样的话,hashset对象中即存在了相同的对象,这就不对了。在上面,我提到了内容相等,这里其实也暗含了复写equals方法,但是,实际应该先复写equals还是先复写hashcode呢,或者说是应该先有复写equals的动机还是先有复写hashcode的动机,学生我也不明白,听起来似乎有点先有鸡还是先有蛋,其实没关系,这其实不是重点,只要记住一点就是了,复写equals的时候我同时复写了hashcode方法,并保证equals相等的对象hashcode相等就可以了。

另外,在别的论坛上也看到说:Hashcode中有一个常用规定,就是equals相等的对象hashcode也要相等。

        于是得出一个重要结论,同时复写equals函数和hashcode函数,也别管谁先谁后的问题。重写equals函数主要基于业务逻辑需要,而重写hashcode函数更侧重语法需要。另外,这个问题的核心其实在于“哈希冲突”,就是前面提到的对于hash函数,可能存在对于不同的输入得到相同的输出,这是一个数学问题,因此需要构造足够强大的哈希函数才能有效减少这个问题,数学上讲哈希冲突只能尽量避免,无法消除,这是个概率问题。

=================

1.当将一个自定义类放入 hashMap,hashSet,hashTable中时,一定要重写hashcode和equals方法。

2.重写equals方法的原则:

离散数学中定义的equals的原则是:

1. 自反性:A.equals(A)要返回true.
2. 对称性:如果A.equals(B)返回true, 则B.equals(A)也要返回true.
3. 传递性:如果A.equals(B)为true, B.equals(C)为true, 则A.equals(C)也要为true. 说白了就是 A = B , B = C , 那么A = C.
4. 一致性:只要A,B对象的状态没有改变,A.equals(B)必须始终返回true.
5. A.equals(null) 要返回false.
通俗来说,按照编程的逻辑,原则是:

  1. 判断是否等于自身;2. 使用instanceof运算符判断 other 是否为同类型的对象;3. 比较类中你自定义的数据域,一个都不能少.

@Override  
    public boolean equals(Object other) {  
        System.out.println("equals method invoked!");  
          
        if(other == this)  
            return true;  
        if(!(other instanceof Coder))  
            return false;  
          
        Coder o = (Coder)other;  
        return o.name.equals(name) && o.age == age;  
    }  
3.重写hashcode方法的原则:(注:此处是重写hashcode的方法之一)

public int hashCode() {
  int result = 17;  //任意素数
 result = 31*result +c1; //c1,c2是什么看下文解释
  result = 31*result +c2;
  return result;
} 其中c1,c2是我们生成的你要计算在内的字段的代码,生成规则如下:

如果字段是boolean 计算为(f?1:0);
如果字段是byte,char,short,int则计算为 (int)f;
如果字段是long 计算为 (int)(f^(f>>32));
如果字段是float 计算为 Float.floatToLongBits(f);
如:

@Override  
    public int hashCode() {  
        int result = 17;  
        result = result * 31 + name.hashCode();  
        result = result * 31 + age;  
          
        return result;  
    }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值