Java探究HashSet中如何完成同一个对象不重复出现(自定义对象作为HashSet的元素时候应该注意重写的hashcode与equals讨论)
2021.10.29 写于山威
1.继承体系
没有学过UML,自己瞎画的
2.问题讨论
我们都知道,在java的set中是不能出现重复的元素的,但是是如何保证这一问题的实现的呢?我们先买个关子,我们先想想,平时我们如何判断元素是否相等:
- 对于基本数据类型:直接使用等于号
- 对于引用对象:使用equals判断,当然也不排除string类这种重写hashcode等,使用hashcode来判断对象是否相等
所以我们一般都会理所应当的想到,在set中要满足插入元素没有重复的时候,一般是使用集合元素的equals方法来判断的,那么我们下面来写一段代码检测一下
//只是重写equals方法
public static class A
{
@Override
public boolean equals(Object obj) {
return true;
}
}
//只是重写hashcode方法
public static class B
{
@Override
public int hashCode() {
return 1;
}
}
//将equals方法和hashcode方法都重写
public static class C
{
public boolean equals(Object obj) {
return true;
}
@Override
public int hashCode() {
return 2;
}
}
public static void main( String [] __)
{
//我们写了三个私有类,接下来我们测试一下
HashSet<Object> test=new HashSet<Object>();
test.add(new A());
test.add(new A());
test.add(new B());
test.add(new B());
test.add(new C());
test.add(new C());
//我们重复的加进去了六个元素,那么按照我们重写之后的方法(无论是什么都返回true与相同的hashcode)
//这个set里面目前还有谁在保留呢?
System.out.println(test);
}
在这种情况下,大家猜测会输出什么呢?
[mytestDemo.Hello$B@1, mytestDemo.Hello$B@1, mytestDemo.Hello$C@2, mytestDemo.Hello$A@7de26db8, mytestDemo.Hello$A@1175e2db]
这是我的输出,可以很明显的看到,输出里面有两个A,两个B,但是只有一个C,原因其实很简单,因为重写方法的时候,我们只有对于C既重写hashcode,又重写了equals
3.源码解释
因此我们可以得到,set判断元素是否重复的时候,既使用了hashcode,又使用equals方法,具体是怎么使用,让我们进入源码看看(稍稍看看,深入看不能我能窥探的)
我们可以看到,在hashset中是有一个hashmap来保证插入的元素是不会重复的(这一点我也没有想到)
具体这里的put方法中有一个参数是hash(value),我们可以看到在这个函数里面其实就是调用了对象的hashcode函数,用得到的这个函数去检索set集合中相应位置是否有元素(set说是无序,其实内部是使用hashcode作为序的),因此会有下面的几种情况
- 重写的hashcode计算得到的数据对应位置没有其他元素,插入进去(最好的情况)
- 没有重写hashcode方法,使用object的hashcode方法返回地址值作为索引,因为地址值唯一,所以一定可以插入(类似于刚刚类A)
- 重写了hashcode方法,但是没有重写equals方法,类似于刚刚类B,这里类B的情况就是对应位置有元素了,使用equals来判断是否相等,因为类B没有写equals方法,使用object的equals方法来判断,得到两个元素是不等的,所以在set中hashcode得到的索引位置建立了单链表,串联起来-->会造成set非常臃肿
- 重写了hashcode和equals方法(规范重写:既如果hashcode相等,则equals一定相等),如同刚刚的类C,所以最终输出只有一个C
下面我来画图让大家看的更加直观,对于刚刚的插入:
大概的讲解就是这么多了,我知识水平有限....