hashcode和equals

      在java中,万物皆对象,所有的对象都继承于Object类,Object类有两个方法equals和hashCode。equals一般用来比较两个对象的内容是否相等,而hashCode一般用来提高容器的查询效率。

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

      equals在没有重写的情况下和==是一样的,对于值类型,比较的是值,对于引用类型则比较的是对象的首地址。

      hashCode我们一般很少直接使用,它返回的是一个int值,在HashMap中对对象进行存储时,它会调用hashCode方法来比较两个对象是否相等。查询对象的时候也会调用hashCode以提高查询效率。

      一般来说equals方法比较相等,则hashCode一定相等,反过来不一定成立,因为具有相同的hashCode不一定是相同的对象。一个好的hashCode函数应该能做到为不同的对象产生不相等的hash值。


      如果我们对equals方法进行重写时,一般强烈建议对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值。因为我们在使用HashMap、HashSet的时候会使用hashCode和equals来判断存入的是否是同一个对象。如果不重写hashCode,那么会继承Object中的,它返回的是一个对象的地址,对于两个对象,这个地址是永远不会相等。如果hashCode都不相等,就不会再调用equals方法进行比较了。

      当从HashSet集合中查找某个对象时,java系统首先会调用对象的hashCode()方法来获得该对象的哈希码,然后根据哈希码找到对应的存储区域,最后取得该存储区域内的每个元素与该对象进行equals方法比较。这样就不用遍历集合中的所有元素就可以得到结论,可见HashSet集合具有很好的对象检索性能。


      下面我们通过几个例子,演示一下对象重写或不重写hashCode与equals能否被存入HashSet中:

public class MyObject {
	public int x;
	public int y;

	public MyObject(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 (getClass() != obj.getClass())
			return false;

		final MyObject myObject = (MyObject) obj;
		if (x != myObject.x || y != myObject.y) {
			return false;
		}
		return true;
	}
}

测试1: MyObject类重写了父类Object中的hashCode和equals方法,如果两个MyObject对象的x y值相等的话,那么他们的hashCode的值就会相等,equals后返回true,测试代码如下:

public class Test {
	public static void main(String[] args) {
		HashSet<MyObject> set = new HashSet<MyObject>();
		MyObject r1 = new MyObject(3, 3);
		MyObject r2 = new MyObject(5, 5);
		MyObject r3 = new MyObject(3, 3);
		set.add(r1);
		set.add(r2);
		set.add(r3);
		set.add(r1);
		System.out.println("size:" + set.size());
	}
}

我们向HashSet中存入了4个对象,打印set集合大小为size:2,为什么为2?因为我们重写了MyObject类的hashCode方法,只要MyObject对象的x,y属性值相等,那么它的hashCode值就是相等的。所以先比较hashCode的值,r1和r2对象的x y属性值不等,那么hashCode就不等,所以r2对象可以放进去。r3对象的x y属性值和r1对象的属性值相同,所以hashCode是相等的,然后再比较r1和r3的equals方法,也是相等,所以r1、r3对象时相等的,所以r3不能放进去。最后一个r1肯定也是放不进去的。


测试2:把MyObject对象的hashCode方法注释,即不重写Object对象的hashCode方法,再运行一下代码

运行结果:size:3

因为hashCode方法没有被重写,使用Object中的hashCode方法返回的是对象的地址,不同的实例对象的hashCode是不同的,所以hashset中可以存入r1,r2,r3


测试3:把MyObject对象中的equals方法注释掉,直接返回false,不注释hashCode方法,运行一下代码:

运行结果:size:3

这个结果让人比较意外,首先r1和r2对象比较hashCode不相等,那么r2放入hashset中。再来看一下r3,比较r1和r3的hashCode方法,是相等的,然后比较他们的equals方法,因为equals始终返回false,所以r1和r3也是不相等的,所以r3可以放入set。再看最后一个r1(为防混淆,我们称它为r4吧),r1和r4 hashCode相等,再比较equals返回false,所以r1和r4不相等,同理r2和r4,r3和r4也不相等,所以r4应该可以放入集合中,那为什么集合的大小是3呢?

我们有必要翻一下HashSet的源码了:

public boolean add(E e) {  
        return map.put(e, PRESENT)==null;  
}  
我们会发现HashSet是基于HashMap实现的,我们打开HashMap的put方法:

    public V put(K key, V value) {
        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中的判断:

if(e.hash == hash && ((k = e.key) == key || key.equals(k)))

首先是判断hashCode是否相等,不相等的话,直接跳过,相等的话,再来比较这两个对象是否相等或者这两个对象的equals方法,因为进行的是“ 或 ” 操作,所以只要有一个成立即可,那这里我们就可以解释了,其实上面的那个集合的大小是3, 因为最后的一个r1没有放进去,因为(k = e.key) == key 即 r1==r1返回true就return了,所以没有放进去了。集合的大小是3,如果我们将hashCode方法设置成始终返回false的话,这个集合就是4了。


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值