Java中的Set接口是Collectio的子接口,Set集合中不允许包含相同的元素。如果添加相同的元素,add()将返回FALSE,并且不会添加新元素。Set集合常用于对数字、字符串等元素进行去重,但是当元素为自定义对象类型时,Set的去重是否符合我们的预期呢?下面将以HashSet为例,通过一系列的实验逐步验证。1、首先创建一个FootBallPlayer2的FootballPlayer类。(假设:HashSet会将属性值相同的对象识别为重复项),为了测试HashSet对对象的去重效果是否与猜测一致,我们先构建三个对象实例,其中构建两个具有相同属性的“C罗”。结果:HashSet没有识别出“C罗”这两个对象是重复的,将三个实例都添加到HashSet集合中。3、在了解HashSet如何去重之前,我们先来看看HashSet是如何实现的。通过查看JDK源码发现HashSet其实是对HashMap进行操作的。4.继续查看hashSet的add()方法,实际上调用了HashMap的put()方法5.继续跟踪,直到putVal()方法(重点)仔细查看putVal()方法,发现是否重复判断新进入的元素是否相等判断hash值是否相等的依据是基于以下两种方法,即通过判断hashCode()方法判断是否相等,使用equals()方法判断是否相等.对比Object和其他对象的equals()方法,发现自定义Object对象equals返回的值为false。接下来我们就一一看看它们的equals实现。(1)Integer对象的equals实现。通过阅读代码,我们发现判断的依据是值是否相等。(2)String对象的equals实现,判断的依据是:先判断引用的对象是否相同,然后逐个比较字符串的值。(3)Object的判断依据是引用的对象是否相同,由于上面两个足球运动员是新建的,不是同一个对象,equlas()返回false7。看完equlas的实现,我们再来看IntegerStringObject的hashCode实现。同样先做个简单的测试,调用他们的hashCode()方法计算hash值,做个对比实验。结果导致两个Object对象的hash值不相等。接下来看看他们对hashcode()的具体实现(1)通过源码发现Integer是通过对其值进行运算得到hash值的。(2)String也是通过计算其值得到hash值,所以测试结果为真(3)查看Object的hashCode()方法,发现并没有具体的实现。根据资料,JDK8的默认hashCode的计算交给了C++。该方法是使用Marsaglia的xorshifschema随机数算法通过一个与当前线程相关的随机数+三个一定的值得到一个随机数。因此,两个不同的对象得到的哈希值是不同的,测试结果也是假的。(这里不深入讨论Object的hashCode(),也欢迎有深入了解的朋友分享)8、了解到HashSet使用hashcode()和equals()去重,自定义Object对象的equals()和hashcode()的实现原理,那么按照我们期望的方式去实现HashSet,当两个对象的所有属性的值都一致时,就认为是同一个对象,我们可以重新设置FootBallPlayer类的equals()和hashcode()来写,代码如下:hashCode()改写为通过计算对象所有属性的值得到的哈希值。equals()改写为先判断引用的对象是否相同,再判断对象的各个属性值是否相等。结果和预期的一样,HashSet对“C罗”对象进行了去重。综上所述,HashSet底层就是HashMap的操作。去重的原理是通过hashCode()和equals()方法判断是否重复。通过实验发现自定义对象去重不成功的原因与JDK默认的Object对象hashCode()和equals()的实现有关。对于自定义对象的去重,我们可以重写自定义对象的hashCode()和equals(),按照我们想要的规则进行去重。
