hashCode()和equals()

个人对hashCode()和equals()方法的理解

前言:

很多java初学者在学习到容器的时候必定逃不开这俩东西,虽然教材上再三强调了equals相等时hashCode()一定相等,hashCode()相等时equals()不一定相等这句话,并且我们大多数情况下对于这个方法的理解也就是记住这句话。

本人第一遍学容器的时候关于这两个东西也是稀里糊涂,但是最近再次复习容器的时候,对这两个方法有了更加深刻的理解

关于equals():

如何理解”相等”这两字?
小学学数学的时候,老师是这样教的:

    定义a = 3,b = 3;
    那我们就说a与b相等。

可见“相等”并不是说两个数’本来’就是一个数,而是说我们给他们添加了一些规则,只要符合了这些规则,我们就认为他们相等

所以当我们重写equals()方法时,一定要记住的是我们是在给该’类’添加我们自己的规则,只要符合了我们自己定义的规则,我们就认为他们相等。(尽管你定义的规则看起来会很扯淡。。。)

//极端例子。。
class Human{
    private int id;
    private String name;
    private int sex;

    public boolean equals(Object o){    //我们定义的规则是:只要两个人性别相等那么就认为这两个人相等。
        return (o instanceof Human) && ((Human)o).sex == this.sex;
    }
}

多么扯淡的相等规则啊!如果按照此规则我和马云‘相等’(尬笑。。。)。但是就算规则在我们看来如此扯淡如此**,但对于java来说我和马云就是相等的。所以规则是我们自己定义的

不过规则也不是随便想怎么写就怎么写的,正确的equals()必须满足下列5个条件:

  1. 自反性:对任意x,x.equals(x)一定返回true
  2. 对称性:对任意x和y,如果y.equals(x)返回true,那么x.equals(y)也必然返回true。
  3. 传递性:对任意x,y,z。如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)必然返回true
  4. 一致性:对于任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)
    多少次,返回的结果应该保持一致
  5. 对任何不是null的x,x.equals(null)一定返回false。
    (我和马云完全符合这五条(尬笑。。。))

当然玩笑归玩笑,equals()方法除了满足上述五条最基本的要求以外,更重要的是符合实际开发环境的需要,千万不可随便定义规则。

关于hashCode():

其实这个才是本篇博客的重点。

首先介绍基本概念:

  • hashCode翻译过来叫散列码
  • hashCode返回的是一个int,可以将这个数理解成int版本的equals(),也就是不完全版本的equals()
  • 使用散列的目的是快速的在一个Set/Map容器中找到相应的对象。说快速是因为可以根据哈希码直接定位到对象,只用做很少甚至不用做任何循环和查找的操作。
  • hashCode的本质和应用只是用来定位对象的,和判断对象是否相等与hashCode()是否唯一没有任何必然联系。

以Map(数组+链表)为例:

  • 往Map中存放一个key,value时,首先由hashCode()得到相应的散列码
  • 然后由相应的规则调整散列码的值直到散列码的值小于数组的容量(一般是取余操作)。
  • 将key,value放入下标为调整过后的散列码数组中。
  • 根据key.equals()来判断在这个“槽位”中的链表是不是有该key值了(Map规则key值只能有一个且不能为null)
  • 若有则更改对应的value,没有加入链表。

可以看出上述过程中,hashCode()只是用来得到相应的“槽位”的,而得到槽位之后和hashCode()就没有关系了,如果是按照取余的规则,那么一个“槽位”甚至是由不同的散列码给组成的(比如5对3取余是2,8对3取余也是2),然后在根据equals()来判定该key是否已经在此槽位中了。

所以再次理解equals相等时hashCode()一定相等,hashCode()相等时equals()不一定相等这句话就非常容易了,我们往Map中存放一个对象,是先根据hashCode()找到“大致位置”(槽位),然后在“大致位置”里面再次用equals()进行寻找。如果hashCode()不相等,那么“大致位置”就已经找错了,里面的任何元素都不会满足equals()的。

同时也应该理解了判断对象是否相等与hashCode()是否唯一没有任何必然联系。,hashCode()只是找了一个大致范围,没有必要说对象的hashCode()一定是唯一的。

最后:

  1. 如果要重写equals方法时,强烈建议甚至必须重写hashCode()方法。

    原因很简单:java规定equals()相等是hashCode()必须相等,而Object类中的两个方法都是和本对象的指针有关的,换句话说都是唯一的,如果只重写equals()方法,那么就会出现hashCode()不等而equals()相等的情况了,这样的话任何使用散列的数据结构(HashSet,HashMap,LinkedHashSet,LinkedHashMap)就无法正确的处理问题了。

    加上强烈建议是因为java语法并没有强制要求这个,还有就是对于不用散列的程序而言就没必要了(比如课后作业。。。)

  2. 设计hashCode()方法的原则:

    • 最重要的因素:无论何时,对同一个对象调用hashCode()都应该产生相同的值。所以如果hashCode()是依赖对象中易变的数据,那就要当心了,因为此数据一旦改变,hashCode()生成一个不同是散列码,相当于产生一个不同的键
    • 另外也不应该使hashCode()依赖与具有唯一性的对象信息,尤其是使用地址这个值,因为这样会产生equals定义的规则通过,但是hashCode()是两个差距非常大的值。 (同时该条也说明了散列码没必要是唯一的)

      例:如果String没有重写hashCode。
          那么  map.put(new String("123"),"aaaa");
                map.put(new String("123"),"bbbb");
      会添加两个'相同'的key值。
      
    • 好的hashCode()应该产生分布均匀的散列码
  3. 重写hashCode()和equals()的示范代码:
    ecplise右键 选Source (或者直接Alt+Shift+s)-> Generate hashCode() and equals() -> 选中想要生成散列码因子的成员变量 -> OK

你可能感兴趣的:(Java)