为什么重写equals时必须重写hashCode方法?

首先equals与hashcode间的关系是这样的:

1、如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)

也就是:

  • 两个对象相等,hashcode一定相等
  • 两个对象不等,hashcode不一定不等
  • hashcode相等,两个对象不一定相等
  • hashcode不等,两个对象一定不等

大家都知道,equals和hashcode是java.lang.Object类的两个重要的方法,在实际应用中常常需要重写这两个方法,但至于为什么重写这两个方法很多人都搞不明白,不懂我们就撸撸源码,看看它到底是什么妖怪。

public native int hashCode();

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

可以看出,Object类默认的equals比较规则就是比较两个对象的内存地址。而hashcode是本地方法(也就是调用了C语言或其他语言写的代码块,native译:本地的),JAVA的内存是安全的,因此无法根据散列码得到对象的内存地址,但实际上,hashcode是根据对象的内存地址经哈希算法得来的。

自我的理解:

由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那就不必在进行equals的比较了,这样就大大减少了equals比较的次数,这就比需要比较的次数少很多,提高效率,一个很好的例子就是集合中的使用;

我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的,(不理解集合的童靴也不用怕,我们就理解成数组,只是这个数组比较特殊,不能存重复的值)那么怎么能保证不能被放入重复的元素呢,只靠equals方法比较的话(equals比较则需要一个个的遍历比较),如果原来集合中有10000个元素了,那么放入第10001个元素,难道要将前面的所有元素都进行比较,看看是否有重复???,这样就是效率就很低了,那有没有什么方法可以提升一下匹配效率呢?因此hashcode就允然而生了,JAVA就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上(这句话需要认真理解),那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,如果有就用equals比较,如果没有则直接插入,这样就大大减少了equals的使用次数,执行效率就大大提高了。

继续上面的话题,为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确实不相同的。说白了就是:保证如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象(可能是同类型的对象,特定的属性相同,实际两个对象的地址不同),hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。

那么我们就用代码来实际演示一下,懂了不代表就会用了,毕竟学了是要用出来的,一起动动小手指吧

没有重写hashcode()和equals()的正常输出结果:

public class Teacher {

	private int id;
	private String name;
	
	public Teacher(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	public static void main(String[] args) {
		
		Teacher an3=new Teacher(1001,"ABC");
		Teacher an4=new Teacher(1001,"ABC");
		
		
		boolean tep = an3.equals(an4);
		System.out.println(tep);//false
		System.out.println(an3);//test.Teacher@15db9742
		System.out.println(an4);//test.Teacher@6d06d69c
	}


	

}

重写了hashcode()和equals()的输出结果:

public class Teacher {

	private int id;
	private String name;
	
	public Teacher(int id, String name) {
		this.id = id;
		this.name = name;
	}

	//重写hashCode()
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	//重写equals()
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Teacher other = (Teacher) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

/*
我们输出的时候System.out.println(an4),默认是调用了toString()方法
也就是System.out.println(an4)和System.out.println(an4.toString())是一样的效果
可以看到输出的也是hashCode()的值,Integer.toHexString(hashCode())表示于十六进制的形式返回
*/
	//Object类中的toString()源码
	/*
	 *  public String toString() {
     *   	return getClass().getName() + "@" + Integer.toHexString(hashCode());
     *		}
	 */

	
	public static void main(String[] args) {
		
		Teacher an3=new Teacher(1001,"ABC");
		Teacher an4=new Teacher(1001,"ABC");
		
		boolean tep = an3.equals(an4);
		System.out.println(tep);//true
		
		System.out.println(an3);//test.Teacher@1793a
		System.out.println(an4);//test.Teacher@1793a
		
		System.out.println(an3.hashCode());//96570
		System.out.println(an4.hashCode());//96570
	}
}

展示了Teacher类重写equals方法和hashcode方法,建议大家用eclipse自动生成,尽量不要自己敲因为很有可能会出错。(不知道怎么自动生成的点这里)

此时an3.equals(an4)一定返回true

  • 假如只重写equals而不重写hashcode,那么Teacher类的hashcode方法就是Object默认的hashcode方法,由于默认的hashcode方法是根据对象的内存地址经哈希算法得来的,显然an3!=an4,故两者的hashcode不一定相等。
  • 然而重写了equals,且an3.equals(an4)返回true,根据hashcode的规则,两个对象相等其哈希值一定相等,所以矛盾就产生了,因此重写equals一定要重写hashcode,而且从Teacher类重写后的hashcode方法中可以看出,重写后返回的新的哈希值与Teacher的两个属性有关。

小总结:

  • hashCode主要用于提升查询效率,来确定在散列结构中对象的存储地址;
  • 重写equals()必须重写hashCode(),二者参与计算的自身属性字段应该相同;
  • hash类型的存储结构,添加元素重复性校验的标准就是先取hashCode值,后判断equals();
  • equals()相等的两个对象,hashcode()一定相等;
  • 反过来:hashcode()不等,一定能推出equals()也不等;
  • hashcode()相等,equals()可能相等,也可能不等。

下面的内容作为了解即可
先来看下String类的源码:可以发现String是重写了Object类的equals方法的,并且也重写了hashcode方法,这就可以进一步的理解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;
    }


public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

也就是:String中的equals()方法进行了重写,比较的是内容而不是地址,而普通引用类型中使用的equals()方法比较的是地址。不单单String中的equals()方法进行了重写,Date、File、包装类等都进行了重写。有兴趣的就自己去看看源码吧,这里就不在一一列举了。

希望多多交流,多多关注,共同成就梦想

参考博客链接:

https://blog.csdn.net/qq_35868412/article/details/89380409

你可能感兴趣的:(work,java)