关于HashSet添加元素时的equals()方法和hashCode()方法

HashSet集合不能存储重复的元素,那么元素之间是否重复,HashSet是根据什么机制去判断的呢?

HashSet在添加一个元素时(比如此时添加的是”a”这个元素),都会将该元素与set中所遍历到的每个元素作比较,比较的过程是这样的:先用该元素的hashCode值与遍历到的每个元素的hashCode作比较,如果hashCode不相等,则直接添加;若hashCode的值一样,则继续用该元素的equals()方法比较(是被添加的equals()方法,与之比较的元素作为参数),如果equals()方法得到的值是一样的,不再添加,如果equals()的值是不一样的,就会将该对象添加到其他内存地址(重新计算出不一样的hashCode)。
下面举个例子:

package cn.test.set;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

public class TestHashSet {
    public static void main(String[] args) {
        Set set = new HashSet();

        set.add(new A());
        set.add(new A());

        set.add(new B());
        set.add(new B());

        C c1 = new C();
        C c2 = new C();

        set.add(c1);
        set.add(c2);
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(set);

    }
}
class A{
    public boolean equals(Object obj){
        return true;
    }
}

class B{
    public int hashCode(){
        return 1;
    }

}

class C{
    public static int count = 0;
    public int a;

    public C(){
        count++;
        a = count;
    }
    public int hashCode(){
        return a;
    }

    public boolean equals(Object obj){
        return true;
    }
}

得到的结果是:

1
2
[cn.test.set.B@1, cn.test.set.B@1, cn.test.set.C@2, cn.test.set.A@152b6651, cn.test.set.A@6bbc4459]

添加到set里的元素有两个B元素,两个A元素和一个C元素。

为什么有两个A元素?
因为A类没有重写hashCode()方法,在new 两个同一个类的对象时,hashCode肯定是不同的,所以两个A都会被添加进去。

为什么会有两个B元素?
B类重写了hashCode(),返回1,首先在set里面有两个A元素的情况下,我们知道没有重写hashCode(),A返回的hashCode()绝对不可能出现返回1的值。当添加第一个B的时候,能添加成功,当添加第二个B的时候,比较第一个B的hashCode,发现相同;相同就继续用被添加的元素的equals去比较,由于没有重写,所以两个new出来的对象用equals()比较也基本不可能相同。

为什么只要一个C,而且C是@2而不是@1?(也就是为什么是hashCode返回2的那个C被添加进去了?)
当添加第一个C的时候,hashSet会将C逐个与其他元素比较(先hashCode()后equals()),当跟第一个B比较的时候,发现他们的hashCode是一样的,那么就继续用第一个C的equals()的方法,第一个B作为参数,发现返回true,hashSet就认为他们是相等的,所以抛弃了,但添加第二个C的时候就不会了。

将类B的hashCode从返回1改为返回100,得到的结果如下:

1
2
[cn.test.set.C@1, cn.test.set.C@2, cn.test.set.B@64, cn.test.set.B@64, cn.test.set.A@152b6651, cn.test.set.A@6bbc4459]

可以用刚刚的思路去理解一遍,发现解得通

=======
所以,建议我们把要添加到hashSet的对象的类最好重写hashCode()方法和equals()的方法,并且equals()相等时,hashCode()也相等。这样的话,只要我们用equals()判断返回true,就一定是相等的对象,如果判断返回false,就一定是不相等的对象

这里有两条原则:

1、当两个对象的equals()相等时,hashCode()返回的值是一样的
2、对象中用作比较equals()的属性,都用来计算hashCode()

这里写图片描述

=====================================================
向HashSet添加可变对象时,必须十分谨慎,不能修改已经添加到HashSet里面的元素,否则会造成HashSet的混乱,看以下代码:

package cn.test.set;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;

public class TestHashSet2 {
    /**
     * 当向hashSet添加可变类的时候必须十分小心,添加到hashSet
     * 后最好不要再修改里面的值,不然会造成混乱
     * @param args
     */
    public static void main(String[] args) {
        HashSet<R> set = new HashSet<R>();
        set.add(new R(5));
        set.add(new R(-3));
        set.add(new R(9));
        set.add(new R(-2));


        System.out.println(set);
        Iterator<R> it = set.iterator();
        R r = it.next();
        r.count = -3;
        System.out.println(set);

        set.remove(new R(-3));
        System.out.println(set);

        System.out.println("set是否包含-3:"+set.contains(new R(-3)));
        System.out.println("set是否包含5:"+set.contains(new R(5)));
    }
}
class R{
     int count;

     public R(int count){
         this.count = count;
     }

    @Override
    public int hashCode() {
        return this.count;
    }

    @Override
    public boolean equals(Object obj) {
        //只有对象的count相等,就认为他们是相等的
        if(obj instanceof R){
            R r = (R)obj;
            if(r.count == this.count){
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        return "R [count=" + count + "]";
    }



}

输出结果:

[R [count=5], R [count=9], R [count=-3], R [count=-2]]
[R [count=-3], R [count=9], R [count=-3], R [count=-2]]
[R [count=-3], R [count=9], R [count=-2]]
set是否包含-3false
set是否包含5false

结果分析:
在第一次输出整个set的时候,没什么问题;然后程序修改了set用迭代器迭代出来的第一个元素(根据结果显示,这个元素是count=5)的count为-3,然后删除了count=-3的元素set.remove(new R(-3));,删除的过程是这样的:根据要删元素计算出他的hashCode值,然后在set找到这个索引的位置,用remove的参数的equals判断是否相同,是的话就删除元素,否则不删;所以这里把真正的count=-3给删除了(不是修改后的),然后程序System.out.println("set是否包含-3:"+set.contains(new R(-3)));,同样的,程序会根据set.contains(new R(-3)) 里的new R(-3)计算hashCode,发现set中没有这个元素,于是报false,接着程序System.out.println("set是否包含5:"+set.contains(new R(5))); ,同理计算new R(5)的hashCode,发现set中的有这个位置的元素,然后用contains()参数的equals判断相不相同,发现不相同,所以报false

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值