在HashSet中添加元素的深度解析

本文通过一个Java集合测试案例详细解析了HashSet如何使用哈希码和equals方法处理元素的添加与删除。当对象的属性值改变时,即使哈希码相同,由于equals返回false,对象仍能被添加到HashSet中,展示了HashSet存储的特性及元素去重逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class Person {

     int id;
     String name;
	
	//带参构造器
    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
	
	//空参构造器
    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
    
	//重写equals和hashCode方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (id != person.id) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}
public class CollectionTest {

    @Test
    public void test3(){
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");

        set.add(p1);
        set.add(p2);
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]

        p1.name = "CC";
        set.remove(p1);
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"CC"));
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"AA"));
        System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]

    }
}

HashSet底层是一个数组,我们把Person的对象p1、p2存入该数组(p1、p2通过各自属性的哈希值和相应算法,确定p1、p2在数组中的位置),p1和p2的哈希值不一样,故他们在数组中的索引位置很可能也不一样。
代码:

    p1.name = "CC";
    set.remove(p1);//p1用的索引,通过这个索引能找到“1001, CC”这个数据(p1还是指向原来的对象,只不过这个对象中的属性值由1001, AA变为1001, CC了)
    System.out.println(set);

set.remove(p1)时,p1用的索引,通过这个索引能找到1001,"CC"这个数据(p1还是指向原来的对象,只不过这个对象中的属性值由1001,"AA"变为1001,"CC"了),先算一下1001,"CC"这个对象的哈希值,通过哈希值和相应地算法找出1001,"CC"在该数组中的位置。而这个位置很大可能不是下图最左边CC的那个位置,因为下图最左边CC的那个索引位置是根据1001,"AA"的哈希值及其相应地算法找出来的。

通过哈希值和相应地算法找出的1001,"CC"在该数组中的位置应为下图最右边的索引位置(假设通过1001,"CC"的哈希值和相应算法算出的1001,"CC"索引位置在下图最右边)。我们remove p1时,即remove 1001,"CC",是在下图最右边位置上寻找并删除1001,"CC",但是最右边的位置是空的,此时remove 1001,"CC"相当于没有remove。

System.out.println(set);时输出[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]

在这里插入图片描述
而接着上述代码再往下:

	set.add(new Person(1001,"CC"));
    System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]

此时是拿1001,"CC"的哈希值和对应算法去算其在数组中的对应的位置(即最右边的位置),同时下图最右边位置空的,不用调用equals方法判断是否存在重复元素,直接插入即可。
即如下图:
在这里插入图片描述
再接着,重新再数组中添加1001,"AA"

	set.add(new Person(1001,"AA"));
    System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]

1001,"AA"可以加入到数组中,虽然1001,"AA"的索引位置被1001,"CC" 占据着(最左边),但是1001,"AA"1001,"CC"equals方法比较时,二者不相同,故1001,"AA"也可以添加成功:
在这里插入图片描述
总结:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出元素a在HashSet底层数组中的存放位置(即为:索引位置);

接下来判断数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
  1. 如果hash值不相同,则元素a添加成功。—>情况2
  2. 如果hash值相同,进而需要调用元素a所在类的equals()方法(看元素具体的内容):
    2.1. equals()返回true,元素a添加失败;
    2.2. equals()返回false,则元素a添加成功。—>情况3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cashapxxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值