写在前面
除了需要掌握集合的基本使用,还需要对Java底层了解的深一点,对我们用好这门语言更有利。
目录
hash集合的一些小细节
- 集合存放的对象增删操作需要设计重写hashCode ,否则导致元素删除失败
- 集合删除元素的原理、
- hashCode属性、
- hash冲突、
开始
一、集合存放的自定义对象增删操作需要设计重写hashCode ,否则导致元素删除失败
先看小栗子
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
public class TestHashCode {
public static void main(String[] args) {
testDelElemFromSet();
System.out.println("==============");
testDelElemFromList();
}
public static void testDelElemFromSet() {
Collection mySet = new HashSet();
//添加元素
mySet.add("xiaosi");
mySet.add(123);
mySet.add(129);
mySet.add(new TestHashCodeStudent("si",20));
//输出mySet的内容
System.out.println(mySet + " 长度为: "+mySet.size());
//删除元素
mySet.remove("xiaosi");
mySet.remove(123);
mySet.remove(129);
mySet.remove(new TestHashCodeStudent("si",20));
//输出mySet的内容
System.out.println(mySet + " 删除元素之后的长度为: "+ mySet.size());
}
public static void testDelElemFromList() {
Collection myList = new ArrayList();
//添加元素
myList.add("xiaosi");
myList.add(123);
myList.add(129);
myList.add(new TestHashCodeStudent("si",20));
//输出myList的内容
System.out.println(myList + " 长度为: "+myList.size());
//删除元素
myList.remove("xiaosi");
myList.remove(123);
myList.remove(129);
myList.remove(new TestHashCodeStudent("si",20));
//输出myList的内容
System.out.println(myList + " 删除元素之后的长度为: "+ myList.size());
}
}
执行结果
可见,对于List和Set集合中存放的 对象元素,Integer 和 String 类型的对象能移除
但是 自定义对象 却不能移除
在自定义的类中 重写继承Object 的 hashCode 方法 和 equals方法
再次测试,发现都删除成功了
但是只重写equals方法呢?
发现List集合删除成功,set集合删除失败
而且List集合会调用equals方法,但是set集合在 没有 重写hashCoe的时候,不会调用equals方法
为什么 重写之后就删除成功了呢,我们需要知道 集合 删除元素的原理
二、集合删除元素的原理
删除元素的时候,参数 是一个 新的对象
拿着这个新的对象去 mySet集合中比对,相等就删除
集合中存储的都是对象,关键是如何比较两个对象相等?
- hashSet集合中使用hashCode来 标识 对象、使用 hashCode + equals解决哈希冲突的问题
- ArrayList集合中使用equals 标识 对象、
三、hashCode、equals
hashCode和equals都是继承自Object的,不重写的情况下
- hashCode是一个 格式为
xxx@16进制地址
- equals默认比较的是地址
重写Object的hashCode,最后经hash算法 根据 该类可选定 的 成员属性 得到一个计算模板
每个对象根据自己的成员属性值 来计算出 自己的 hashCode值,标识这个对象
因此
结合前面
-
自定义对象两个方法都不重写—》都删除失败
-
自定义对象只重写equals方法—》List集合删除成功且调用了equals方法,Set集合删除失败且没调用equals方法
-
自定义对象 两个方法都重写了—》都删除成功
- set集合在存储前,调用了一次hashCode,删除过程中,先调用了hashCode,hashCode发现相等之后(开始hashCode未重写的时候,两个对象的hashCode 一定不相等,即在内存中的位置一定不相等,故不会调用equals),又调用了equals,两者都相等,认为这两个对象 相等
得出集合删除数据是
-
List集合:调用重写之后的equals,比较自定义指定的成员属性值;未重写则调用Object的比较 两对象的地址
-
Set集合:依赖于 参数对象(具体点是和hashCode相关的对象属性值) 来和 集合中存在 的元素比对,
先比较hashCode是否相等----》接着比较equals----》两者都相等,认为 两对象相等
(这也就是对象两对象" == "
比较的流程)
String 和 Integer 是同样的原理,只不过是 Oracle 已经写好了 对应的hashCode 个 equals方法
拓展
- 对于字符串常量池的字符串
"=="
返回为true
相等,因为一个对象的hashCode一定相等,而new String("abc")
存储地址在一般的堆区,称为副本对象可以多个,计算出的hashCode不同,直接返回false
- 对于-128-127之间的int值,可以进行自动拆箱和装箱,
"=="
返回为true
相等,因为一个对象的hashCode一定相等,而越界的 会是像 new Integer(400) 存储地区在一般的堆区,计算出的hashCode不同,直接返false
四、hash冲突
对于不同的对象,重写hashCode之后,hash算法得出来的hashCode 可能出现相等的情况
因为set集合元素具有 无序、不允许重复的特点
重写过hashCode方法之后,创建对象存入HashSet的集合,会先计算hashCode值,根据hashCode值确定存放的位置
如果两个对象的 hashCode相同,但是equals是不同的
(例如,hashCode有 姓 计算出来,equals 不仅包括姓,还包括名,现在需要存储 同姓不同名的两个对象)
4.1这时会 引发 哈希冲突
出现哈希冲突的时候,简单理解,通俗理解冲突的元素都会被放在一个 hash桶中
当我们删除元素的时候,不仅会根据hashCode属性,还需要根据equals 才能找到 待删除的 元素
因此,这就要求我们在设计 类的时候,为了避免哈希冲突而引起的 集合删除对象元素失败
需要我们精心设计hashCode和equals函数,提高程序的健壮性