HashSet和HashMap一直都是JDK中最常用的两个类,HashSet要求不能存储相同的对象,HashMap要求不能存储相同的键。 那么Java运行时环境是如何判断HashSet中相同对象、HashMap中相同键的呢?当存储了“相同的东西”之后Java运行时环境又将如何来维护呢? 
在研究这个问题之前,首先说明一下JDK对equals(Object obj)和hashcode()这两个方法的定义和规范:

(1)在Java中任何一个对象都具备equals(Object obj)和hashcode()这两个方法,因为他们是在Object类中定义的。 equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。 hashcode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。

(2)接下来有两个个关于这两个方法的重要规范(我只是抽取了最重要的两个,其实不止两个):

规范1若重写equals(Object obj)方法,有必要重写hashcode()方法(避免不必要的麻烦),确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该 相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。

规范2:如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。 根据这两个规范,

可以得到如下推论

1、如果两个对象equals,Java运行时环境会认为他们的hashcode一定相等。

2、如果两个对象不equals,他们的hashcode有可能相等。

3、如果两个对象hashcode相等,他们不一定equals。

4、如果两个对象hashcode不相等,他们一定不equals

(3)

对于一般类的对象(除String等封装类型对象外):

若普通类没有重写hashcode()和equals()方法,,那么其对象在比较时,是继承的object类中的hashcode()方法,object类中的hashcode()方法是一个本地方法,对该方法的返回值进行比较时,比较的是对象的地址(引用地址),使用new方法创建内容相同的对象,两次生成的当然是不同的对象。除非重写hashcode()方法。在object类中定义的equals()方法也是对对象地址的比较。一句话总结:若不重写普通类的hashcode()和equals()方法,在Set集合中对象引用地址不一样,对象即不重复。

对于String等对象(String、Integer、Double····等等):

由于这些封装类本身已经重写了hashcode()方法,并且重写的方法的返回值跟对象的内容相关,而不是跟引用地址相关。这些封装类中的equals()方法同样进行了重写,比较的是对象的内容,而非引用地址。一句话总结:String等类的对象在集合中均比较他们的内容,内容相同则覆盖已存在的对象

(4)set集合为什么可以保证元素的不重复性,根本是:在添加元素时会进行是否相等的判断。根源是:

Java中的Set集合 怎么保障不重复?

大家可能都知道Set是一个无序的不可以重复的集合。凡事想一个为什么? 
打开源码看一眼: 
Set是一个接口,常用的Set实现类那就是HashSet了。 
------------------------------------------------- 
public HashSet() { 
map = new HashMap<E,Object>(); 
    } 
-------------------------------------------------

这是HashSet的构造方法。 可以看到HashSet内部其实就是一个HashMap 
【添加元素的方法】 
/**  
     * 如果此set中尚未包含指定元素,则添加指定元素。  
     * 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2))  
     * 的元素e2,则向此set 添加指定的元素e。  
     * 如果此set已包含该元素,则该调用不更改set并返回false。  
     *  
     * 底层实际将将该元素作为key放入HashMap。  
     * 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key  
     * 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true),  
     * 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变,  
     * 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中,  
     * 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。  
     * @param e 将添加到此set中的元素。  
     * @return 如果此set尚未包含指定元素,则返回true。  
     */ 
public boolean add(E e) { 
return map.put(e, PRESENT)==null; 
    } 
看一下map的定义: 
HashSet 中定义了这样的两个变量: 
    private transient HashMap<E,Object> map; 
    private static final Object PRESENT = new Object(); 
我们再看一下上面的 add() 方法。 
map.put(o, PRESENT)==null 
实际执行的是 map 的方法,并且我们添加的对象是 map 中的 key,value 是执行的同一个对象 PRESENT. 
因为map中的key是不允许重复的,所以set中的元素不能重复。 
============================================================================== 
根本问题还是没有解决那继续问?为什么HashMap中的Key不允许重复? 
HashMap的往里放元素的源码!!!

public V put(K key, V value) { 
        if (key == null) 
            return putForNullKey(value); 
        int hash = hash(key.hashCode()); 
        int i = indexFor(hash, table.length); 
        //遍历链表 
        for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
            Object k; 
            //如果key在链表中已存在,则替换为新value  HashMap 可以重复put同一个Key值不同的对象。 
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value; 
                e.value = value; 
                e.recordAccess(this); 
                return oldValue; 
            } 
        }

modCount++; 
        addEntry(hash, key, value, i); 
        return null; 
    }

分步骤判断添加的Key值: 
1、取到新添加Key值得hashCode值。 
2、确定数组的index 根据Key的hashCode值和当前table的长度按位取并  h & (length-1); 
    按位取并,作用上相当于取模mod或者取余%。 
    这意味着数组下标相同,并不表示hashCode相同 
3、这里的hashcode在equals前面,JVM会先判断或运算||的前部分,当这一前部分为true的时候判断终止,返回true(这是为了提高JVM的效率),所以当hashcode不同的时候,equals是不会执行的。

总结为什么Set里面不能有重复? 
因为HashMap在put一个Key时会判断,将要放进去的Key的hash值与 目前HashMap中定位到的那个Key的hash值比较。 
如果hash值相当,继续比较 这两个对象的地址或者内容是否相当。 
如果相当:判断出来要添加的Key与HashMap中的Key重复,把Value的值给替换成最新的。 
HashSet中的Value是一个固定值PRESENT。 所以修改不修改无所谓。

HashCode与Equals回顾的更多相关文章

  1. Java hashCode() 和 equals()的若干问题

    原文:http://www.cnblogs.com/skywang12345/p/3324958.html 本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() ...

  2. Java hashCode() 和 equals()的若干问题解答

    本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equa ...

  3. (转)从一道面试题彻底搞懂hashCode与equals的作用与区别及应当注意的细节

    背景:学习java的基础知识,每次回顾,总会有不同的认识.该文系转载 最近去面试了几家公司,被问到hashCode的作用,虽然回答出来了,但是自己还是对hashCode和equals的作用一知半解的, ...

  4. hashCode() 和equals() 区别和作用(转)

    出处:https://www.jianshu.com/p/5a7f5f786b75 本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么 ...

  5. 深入探究Java中hashCode()和equals()的关系

    目录 一.基础:hashCode() 和 equals() 简介 equals() hashCode() 二. 漫谈:初识 hashCode() 与 equals() 之间的关系 三. 解密:深入理解 ...

  6. 对hashcode、equals的理解

    1.首先hashcode和equals都是java每个对象都存在的方法,因为他们两是Object的方法. 2.hashcode方法默认返回的是该对象内存地址的哈希码,然而你会发现,Object类中没有 ...

  7. java中hashcode()和equals()的详解

    今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36). 1. 首先equals()和hashc ...

  8. java中的hashcode()和equals()

    equals()和hashcode()都继承自object类. equals() equals()方法在object类中定义如下: public boolean equals(Object obj) ...

  9. 【Java】Map杂谈,hashcode()、equals()、HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap

    参考的优秀文章: <Java编程思想>第四版 <Effective Java>第二版 Map接口是映射表的结构,维护键对象与值对象的对应关系,称键值对. > hashco ...

随机推荐

  1. 3.5星|《算法霸权》:AI、算法、大数据在美国的阴暗面

    算法霸权 作者在华尔街对冲基金德绍集团担任过金融工程师,后来去银行做过风险分析,再后来去做旅游网站的用户分析.后来辞职专门揭露美国社会生活背后的各种算法的阴暗面. 书中提到的算法的技术缺陷,我归纳为两 ...

  2. pip安装Crypto注意事项

    pip install PyCrypto 1.使用pip install Crypto的方式安装的文件夹名称为crypto,而内部引用都用的Crypto路径,因此pip安装后,需要将文件夹名称修改为C ...

  3. app开发相关

    app播放UIWebview 没有声音解决: 设置 allowsInlineMediaPlayback  = YES; mediaPlaybackRequiresUserAction = NO

  4. 团队Alpha冲刺(八)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...

  5. C++进阶之_类型转换

    C++进阶之_类型转换 1.类型转换名称和语法 C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是: TYPE b = (TYPE)a C++风格的类型转换提供了4种类型转换操 ...

  6. 树莓派两用优盘制作(FAT32存储+EXT树莓派系统)

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:树莓派两用优盘制作(FAT32存储+EXT树莓派系统)     本文地址:http://tec ...

  7. 用putty玩linux的时候由于以前用window 习惯写完东西按一下ctrl+s 保存

    问题描述:用putty玩linux的时候由于以前用window 习惯写完东西按一下ctrl+s 保存,但是在putty一按下就不能再输入了.后来查找到:ctrl+s 是putty的一个命令大概是这样子 ...

  8. node进程捕捉错误

    var childProcess = require('child_process'); var commitMessage = (function() { var spawn = childProc ...

  9. linux安装py3.6

    随手记录: https://www.python.org/ftp/python/3.6.8/Python-3.6.8rc1.tgz 所有linux版本: https://www.python.org/ ...

  10. Java JVM- jstat查看jvm的GC情况[转]

    ava通过jvm自己管理内存,同时Java提供了一些命令行工具,用于查看内存使用情况.这里主要介绍一下jstat.jmap命令以及相关工具. 一.jstat查看 gc实时执行情况 jstat命令命令格 ...