为什么重写equals方法后一定要重写hashCode方法?

·  阅读 86
为什么重写equals方法后一定要重写hashCode方法?

hashCode方法:

  hashCode方法存在就是为了支持哈希容器(以下都称Hash容器),如:HashMap,HashSet等容器,它们是通过hashCode来计算储存的位置的。

添加自定义类实例到哈希容器:

如果我们要把 自定义 的类的 实例对象 添加进Hash容器:

  1. 作为key添加进HashMap:HashMap的key不能重复
  2. 添加进HashSet:HashSet不允许重复元素
  3. (再偷偷告诉你一个秘密,HashSet是由HashMap实现的)

为什么要重写hashCode:

  如果要实现1和2,我们需要重写这两个方法,为什么呢?首先来说明为什么要重写hashCode方法,因为Hash容器需要使用对象的hashCode来计算对象存放的位置,而自定义对象继承Object对象的hashCode方法对于每一个不同的对象都返回不同的hashCode方法,那么对于两个属性相同的对象,比如:
person1={name: "张三", age: 20}, 和 person2={name: "张三", age: 20},
如果要同时把它们放在Hash容器中:
map.put(person1,PRESENT); map.put(person2,PERSENT);
那么它们就会占据两个空间,因为它们的hashCode不一样,所以Hash容器会将它们都储存到容器中,可是它们的属性完全相等,这在逻辑上是错误的,因此我们需要重写hashCode,让各项属性相同的对象拥有相同的hashCode。这里以上面例举的person为例重写hashCode方法,不一定合适,实际使用的时候以自己的需求为准:

class Person {
    private String name;
    private int age;
    // 省略getter、setter、toString
    // 将hashCode与属性关联起来,这样就可以保证对于属性相同的对象,它们的hashCode也相同
    public int hashCode(){
        int hash = 0;
        if(this.name != null){
            int temp = 1;
        	hash = hash<<4 + temp*this.name.hashCode();
            hash = hash<<4 + temp*this.age;
        }
        return hash;        
    }
}
复制代码

为什么要重写equals方法:

  那么为什么要重写equals方法呢?因为使用hashCode计算储存位置和内存地址不一样,不能保证唯一性,我们将自定义对象加入Hash容器时,可能会出现两个不同的对象的Hash值是一样的,这个就是hash冲突,为了处理这个问题,jdk7以前,Hash容器选择使用链地址法来处理冲突,也就是说,在一个位置上可能存在多个以链表储存的对象,那么查询的时候怎么去识别要查询的目标呢?查询的时候需要使用到equals方法来进行比较了,如果比较相等的话就代表查询目标找到了。

  如不重写equals方法,默认的equals方法是继承Object类中的equals方法,底层是使用==符号进行地址判断的,也就是说,只有内存地址相等的对象才会被认为相等,这代表我们如果想要使用其他的属性相同的对象来查询是不可能的,只能使用已经储存在Hash容器中的对象在外部进行查询才能成功:这很不合理,我已经有这个对象了,还去查什么查?多此一举。hash容器只需要两个类的各项属性都相等的话就判断这两个类相等。默认的equals方法不满足要求,所以我们需要重写。如果使用上述的例子来说明的话,equals方法就可以这么写,不一定合适,实际使用的时候以自己的需求为准:

class Person{
    private String name;
    private int age;
    // 省略getter、setter、toString
    // 属性相等就判断为相等,返回true,否则返回false
    public boolean equals(Object obj){
    	if(obj == null) return false;
        if(obj instanceOf Person) {
            Person person = (Person) obj;
            return obj.getName().equals(this.name) && obj.getAge()==this.age;
        }
        return false;
	}
    ...
}

复制代码

总结一下:

  1. hashCode方法是为了支持哈希(Hash)容器而存在的
  2. Hash容器是通过hashCode来计算储存的位置的,但通过hashCode来计算的储存位置不能保证唯一性,可能会产生哈希冲突,jdk7以前Hash容器是通过链表储存来解决哈希冲突的。(jdk8以后还涉及到红黑树,笔者现在还不太熟悉,就不献丑了)
  3. 如不重写hashCode方法,两个属性相同的对象的hashCode不同,无法正确实现Hash容器的唯一性要求
  4. 如果不重写equals方法,对于在相同位置以链表储存的多个哈希冲突的元素就无法判断目标元素是哪一个
分类:
代码人生
标签:
分类:
代码人生
标签: