equals和hashCode方法详解

一、初识equals()和hashCode()方法

 

1、首先需要明确知道的一点是:hashCode()方法和equals()方法是在Object类中就已经定义了的,所以在java中定义的任何类都会有这两个方法。原始的equals()方法用来比较两个对象的地址值,而原始的hashCode()方法用来返回其所在对象的物理地址,下面来看一下在Object中的定义:

    public boolean equals(Object obj) 
    {
        return (this == obj);  //比较的是两个对象的地址是否相同
    }

hashCode:

public native int hashCode();//对应一个本地实现

2、如果定义了一个类A,只要它没有重写这两个方法,这两个方法的意义就是如上面所述。请看下面的示例:

示例一

 

class Rect
{
	int x;
	int y;
	public Rect(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
}
public class Temp
{
	public static void main(String[] args)
	{
		Rect r1 = new Rect(1,2);
		Rect r2 = new Rect(1,2);
		System.out.println("r1.equals(r2)为:" + r1.equals(r2));
		System.out.println("r1的hashCode为:" + r1.hashCode());
		System.out.println("r2的hashCode为:" + r2.hashCode());
	}
}
运行结果:

r1.equals(r2)为:false   //二者内存地址不同,即不是同一对象
r1的hashCode为:319454176
r2的hashCode为:357218532

二、equals()和hashCode()的重写

 

前面说到,因为这两个方法都是在Object类中定义,所以java中定义的所有类都会有这两个方法,并且根据实际需要可以重写这两个方法,当方法被重写后,他们所代表的意义就会发生变化,看下面的代码:

示例二

	 public static void main(String args[])
	 {
	  String s1=new String("abc");
	  String s2=new String("abc");
	  System.out.println(s1.equals(s2));//输出true,因为String类重写了equals()方法,虽然s1和s2此时指向的地址空间不同,它比较的不再是地址而是String的内容, 此时s1和s2都是"abc",故返回true
	  System.out.println(s1.hashCode());
	  System.out.println(s2.hashCode());//你会发现s1和s2的hashCode相同,因为String同样重写了hashCode()方法,返回的不再是地址,而是根据具体的字符串算出的一个值
	 }
运行结果:

true
96354
96354

 三、equals()和hashCode()方法是怎样被重写的

1、我们来看一下String类中这两个方法的定义:

equals()方法:

	    public boolean equals(Object anObject)
	    {
	        if (this == anObject) //首先比较两个对象地址;若相同,必是同一对象 
	        {
	            return true;
	        }
	        if (anObject instanceof String) 
	        {
	            String anotherString = (String) anObject;
	            int n = value.length;
	            if (n == anotherString.value.length) 
	            {
	                char v1[] = value;
	                char v2[] = anotherString.value;
	                int i = 0;
	                while (n-- != 0) 
	                {
	                    if (v1[i] != v2[i])   //比较String中每个字符
	                            return false;
	                    i++;
	                }
	                return true;
	            }
	        }
	        return false;
	    }
}

hashCode()方法:

	    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];     //根据String中每个字符确定hashCode;若两个字符串的内容相同,hashCode便相同
	            }
	            hash = h;
	        }
	        return h;
	    }

2、Integer中两方法的定义:

equals()方法:

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
hashCode方法:

    public int hashCode() {
        return value;
    }

3、小结:

8中基本数据类型的hashCode方法直接就是数值大小,String的hashCode是根据字符串内容计算得的。

8中基本数据类型的equals方法直接比较值,String的equals方法比较的是String的内容。

四、HASH算法简介:

由于记录在线性表中的存储位置是随机的,和关键字无关,因此在查找关键字等于给定值的记录时,需将给定值和线性表中的关键字逐个进行比较,查找的效率基于历经

历经比较的关键字的个数。hash表就是为了实现在记录的关键字和其存 储位置之间建立一个确定的函数关系f,即将关键字为key的记录存储在f(key)的位置上,这样在记录中查找关键字时,只需要根据该关键字先计算出其在整个记录中的位置便可找到,不必挨个遍历了。 将hash算法应用到HashSet集合中提高在集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象存储一个哈希码,然后将哈希码分组,每组对应一个存储区域,根据一个对象的哈希码就可以确定该对象的存储区域(采用hash函数来显示,例如:假设有n个存储区域,利用哈希码与n相除的余数来确定对象存储区域,若存储区域相同,便进行再散列)。

Object类中定义了一个hashCode方法来返回每个对象的哈希码,然后根据哈希码找到相应的存储区域,最后取得该区域内的每个元素与该对象就行equals比较,这样

就不用遍历集合中的所有元素就可以得出结论,可见HashSet具有很好的检索性能。但是,HashSet存储对象的效率略低一些,因为向HashSet中添加一个元素时,要先计算该对象的哈希码,并根据哈希码确定该对象要存储的存储区域。为了保证一个类的每个对象都能在HashSet中正常存储,要求该类的实例对象在equals方法比较相同时,它们的hashCode必须也是相同的。即:obj1.equals(obj2)为true的话,obj1.hashCode()==obj2.hashCode()也为true。换句话说,当我们重写一个类的equals方法时,必须重写它的hashCode方法。如果不重写hashCode方法的话,Object对象中的hashCode方法返回的始终是一个对象的hash地址,而这个地址是永远不相等的,这时,即使重写了equals方法也是没用的,因为如果两个对象的hashCode不相等的话,这两个对象就会被分到不同的存储区域去了,自然就没机会用equals方法进行比较了。


五、equals()和hashCode()方法的应用

HashSet集合:

Hashsetjava中一个非常重要的集合类,Hashset中不能有重复的元素,当一个元素添加到集合中的时候,Hashset判断元素是否重复的依据是这样的:

1)判断两个对象的hashCode是否相等 
   如果不相等,认为两个对象也不相等,完毕 
   如果相等,转入2) 
  (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的)
2)判断两个对象用equals运算是否相等 
   如果不相等,认为两个对象也不相等 
   如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键
  为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。

示例三

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		if(obj == null)
			return false;
		if(this.getClass() != obj.getClass())
			return false;
		final RectObject other = (RectObject)obj;
		if(this.x != other.x)
			return false;
		if(this.y != other.y)
			return false;
		return true;
	}	
}
public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("当前HashSet中元素个数:" + hashSet.size());
	}
}
运行结果:
当前HashSet中元素个数:2
分析:

我们重写了Object中的hashCode和equals方法,当两个对象的x、y值都相等时,它们的hashCode值是相等的,并且equals比较的结果为true。
先添加rect1到HashSet中;在添加rect2时,由于hashCode不等,因而可以添加;添加rect3时,rect3与rect1的hashCode值相等,因而进行equals的比较,为true,不会添加;在添加rect4时,由于与rect1相同,不会添加。
示例四(即不重写hashCode方法)

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
//	@Override
//	public int hashCode()
//	{
//		final int prime = 31;
//		int result = 1;
//		result = prime * result + x;
//		result = prime * result + y;
//		return result;
//	}
	@Override
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		if(obj == null)
			return false;
		if(this.getClass() != obj.getClass())
			return false;
		final RectObject other = (RectObject)obj;
		if(this.x != other.x)
			return false;
		if(this.y != other.y)
			return false;
		return true;
	}
}

public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("当前HashSet中元素个数:" + hashSet.size());
	}
}
运行结果:

当前HashSet中元素个数:3
分析:先将rect1添加至HashSet中;添加rect2,rect3时,三者的hashCode均不相同,因而全部添加至集合中;因rect1 == rect1,不会添加;因而集合中有rect1、rect2、rect3三个对象。
示例五 (将重写的equals方法改为直接返回false)

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj)
	{
//		if(this == obj)
//			return true;
//		if(obj == null)
//			return false;
//		if(this.getClass() != obj.getClass())
//			return false;
//		final RectObject other = (RectObject)obj;
//		if(this.x != other.x)
//			return false;
//		if(this.y != other.y)
//			return false;
//		return true;
		return false;
	}
}

public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("当前HashSet中元素个数:" + hashSet.size());
	}
}
运行结果:

当前HashSet中元素个数:3
分析:首先添加rect1到集合中;比较rect2与rect1的hashCode不同,将rect2添加至集合;rect3与rect1的hashCode相同,equals比较为false,将rect3添加至集合;然后是rect1,rect1与rect1的hashCode相同,equals比较为false,所以应该可以添加第二个rect1到集合中,结果应为4,但事实上却是3,下面来分析:

先看HashSet中add方法源码:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
HashSet时基于HashMap实现的,我们来看HashMap中put的源码

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
主要看if判断条件:

显示判断两对象hashCode是否相等,不等的话,直接跳过后面的判断;相等的话,再来比较两个对象是否相等或者这两个对象的equals方法,因为是或运算,只要一个成立即可。这样,我们刚才在放最后一个rect1时,由于rect1 == rect1,所以最后一个rect1并没有放进去,所以集合大小为3。

参考文档:http://blog.csdn.net/jiangwei0910410003/article/details/22739953

 
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值