@Test public void test() { String str1=new String("abc"); String str2=new String("abc"); boolean equals = str1.equals(str2); System.out.println(equals);//true }
hashCode方法是經過必定的算法獲得一個hash值,通常配合散列集合一塊兒使用,如HashMap、HashSet都是不能夠存放重複元素的,那麼當容器中元素個數不少時,你要添加一個元素時,難道一個一個去equals比較?固然這是能夠的,可是不免效率很低,而HashMap和HashSet的底層都是使用數組+鏈表的方式實現的,這樣有什麼好處呢,當一個對象要加入集合,直接用hashCode進行一些運算獲得保存的數組下標,再去數組下標對應的鏈表中一個一個元素比較(equals),這樣顯然減小了比較次數,提升了效率java
那Object的hashCode方法的默認實現是怎樣的呢?node
public native int hashCode();
能夠看到它是一個本地方法,實際上Object的hashCode方法返回是元素的地址(不一樣的虛擬機可能不同,但Hotspot的是)程序員
class Emp{ String idCord; String name; int age; public Emp(String idCord, String name, int age) { super(); this.idCord = idCord; this.name = name; this.age = age; } } @Test public void test2() { Emp e=new Emp("0101001","zhangsan",20); System.out.println(e.hashCode());//1717159510 System.out.println(e);//com.moyuduo.test.Emp@6659c656 }
6659c656轉換成十進制也就是1717159510算法
咱們不少時候這樣使用HashMap數組
@Test public void test3() { HashMap<String,Emp> map=new HashMap<>(); map.put(new String("zhangsan"), new Emp("01001","zhangsan",20)); map.put(new String("lisi"), new Emp("01002","lisi",22)); map.put(new String("zhangsan"), new Emp("01003","zhangsan",23)); Emp emp = map.get("zhangsan"); System.out.println(emp);//Emp [idCord=01003, name=zhangsan, age=23] }
額?不對呀,編號爲01001的張三呢?並且咱們知道new出來的String的hashCode是地址必定是不相同的,那麼爲何後一個張三仍是把前一個張三覆蓋了呢?app
是由於String重寫了hashCode方法和equals方法this
public int hashCode() { //默認爲0 int h = hash; if (h == 0 && value.length > 0) { char val[] = value; //一個一個遍歷String的char[] for (int i = 0; i < value.length; i++) { //hash值等於當前字符前字符的hash*31+當前字符的Unicode h = 31 * h + val[i]; } hash = h; } return h; } public boolean equals(Object anObject) { //判斷兩個對象的地址是否相同 if (this == anObject) { return true; } //判斷傳入的對象是否是String類型 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //判斷兩個String的char[]的長度是否一致 if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; //一個一個字符進行比較 while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
那麼當咱們使用本身的對象做爲鍵時code
@Test public void test4() { HashMap<Emp,Integer> map=new HashMap<>(); map.put(new Emp("01001","zhangsan",20),6000); map.put(new Emp("01002","lisi",22),8000); Integer integer = map.get(new Emp("01001","zhangsan",20)); System.out.println(integer);//null }
能夠看到輸出的是null,這是爲何呢,就是由於咱們自定義的類沒有從新寫hashCode方法,get的時候新new出來的Emp對象的hashCode(也就是地址)確定和存的時候的hashCode不同,因此拿不到,因此當咱們自定義的類要使用散列集合存儲時,必定要重寫equals方法和hashCode方法對象
爲何當咱們要使用自定義對象做爲key存放在HashMap中時,必定要重寫equals和hashCode呢?get
咱們去看看HashMap底層是怎麼存鍵值對和獲得值的
HashMap的put方法
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //計算鍵的hash static final int hash(Object key) { int h; //若是鍵爲null那麼hash爲0,這也是爲何HashMap只能存放一個鍵爲null的元素,不然hash爲hashCode與上hashCode無符號右移16位 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //若是當前的Node數組還未初始化或長度爲0 if ((tab = table) == null || (n = tab.length) == 0) //進行擴容 n = (tab = resize()).length; //節點存放的下標是數組長度-1與上鍵的hash if ((p = tab[i = (n - 1) & hash]) == null) //運算獲得的下標的位置沒有存放元素,那麼直接保存 tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //若是下標位置元素的hash和鍵的hash相等而且下標元素的key和鍵的地址相同或equals那麼直接覆蓋 e = p; else if (p instanceof TreeNode) //若是下標元素位置存放的元素原本就是紅黑色節點,那麼按照紅黑樹的規則插入 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //下標位置有元素並且還沒轉化爲紅黑樹,說明是鏈表存儲 for (int binCount = 0; ; ++binCount) { //讓e指向鏈表下一個節點 if ((e = p.next) == null) {//當找到最後e等於null了說明鏈表中沒有元素的key和當前插入的key相同 //直接把節點掛到鏈表尾 p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //若是找到已存入元素的key和插入key的hash相同而且兩key地址相等或equals,那麼e就是要替換的元素 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) //替換舊值 e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) //插入後元素大小超過閾值進行擴容 resize(); afterNodeInsertion(evict); return null; }
HashMap的get方法
public V get(Object key) { Node<K,V> e; //若是經過key拿到的鍵值對節點爲null就返回null,不然返回節點的value return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //Node[]是否已經初始化而且長度>0而且經過hash運算獲得的下標已經有元素 if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //判斷下標第一個位置節點的hash和查詢key的hash一致而且兩key地址同樣或equals if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; //下標節點還有next if ((e = first.next) != null) { //節點是紅黑樹,那麼按照紅黑樹的查找規則進行 if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //是鏈表,那麼依次遍歷 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }
能夠看到存的時候是經過hashCode獲得hash再用hash獲得存放下標,而後存入鍵值對
取的時候是經過hashCode獲得hash再獲得下標元素,下標元素再根據hash&&(地址相等||equals)獲得鍵值對
說了這些再來講說Object規範