咱們公司招人喜歡問算法題和一些基礎知識。今天咱們一個面試官在面試候選人以前在辦公室對咱們說他準備問一個這樣的問題: java
在 HashMap 中存放的一系列鍵值對,其中鍵爲某個咱們自定義的類型。放入 HashMap 後,咱們在外部把某一個 key 的屬性進行更改,而後咱們再用這個 key 從 HashMap 裏取出元素,這時候 HashMap 會返回什麼? 面試
咱們辦公室幾我的答案都不一致,有的說返回null,有的說能正常返回value。但不論答案是什麼都沒有確鑿的理由。我以爲這個問題挺有意思的,就寫了代碼測試。結果是返回null。須要說明的是咱們自定義的類重寫了 hashCode 方法。我想這個結果仍是有點意外的,由於咱們知道 HashMap 存放的是引用類型,咱們在外面把 key 更新了,那也就是說 HashMap 裏面的 key 也更新了,也就是這個 key 的 hashCode 返回值也會發生變化。這個時候 key 的 hashCode 和 HashMap 對於元素的 hashCode 確定同樣,equals也確定返回true,由於原本就是同一個對象,那爲何不能返回正確的值呢? 算法
先來看看一段測試代碼: 數組
先解釋一下測試代碼作到事。定義了一個person類,就兩個屬性。重寫了 hashCode 方法,還有一套geter和seter,沒什麼特別。測試類裏面先建立了三個person對象做爲 key 。打印各個 key 的 hashCode 值。而後三個元素放到 HashMap ,接着更新其中一個 key 的name屬性,最後去取這個 key 的value。 ide
package hashmap; public class Person { private String name; private int height; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } @Override public String toString() { return "Person [name=" + name + ", height=" + height + "]"; } @Override public int hashCode(){ // System.out.println(this.name+": HashCode() invoked!"); return this.name.hashCode()+this.height; } }
package hashmap; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; public class HashmapTest { public static void main(String[] args) { // TODO Auto-generated method stub Map<Person, String> testMap = new HashMap<Person, String>(); Person p1 = new Person(); p1.setName("whu"); p1.setHeight(100); Person p2 = new Person(); p2.setName("nlp"); p2.setHeight(1000); Person p3 = new Person(); p3.setName("lcz"); p3.setHeight(10000); System.out.println(p1+":"+p1.hashCode()); System.out.println(p2+":"+p2.hashCode()); System.out.println(p3+":"+p3.hashCode()); testMap.put(p1, "p1"); testMap.put(p2, "p2"); testMap.put(p3, "p3"); System.out.println("---------"); p2.setName("nlp mode"); System.out.println("修改以後的:"+p2+":"+p2.hashCode()); System.out.println("---------"); for(Entry<Person, String> entry:testMap.entrySet()){ System.out.println(entry.getKey()+":"+entry.getValue()+":"+entry.getKey().hashCode()); if(entry.getKey().getName().equals("whu")){ System.out.println("沒有改變"+(entry.getKey() == p1)); } else if(entry.getKey().getName().equals("nlp mode")){ System.out.println("已經發生改變"+(entry.getKey()==p2)); } System.out.println(); } System.out.println("---------"); String p =testMap.get(p2); System.out.println("最後的結果"+p); } }
輸出: 函數
Person [name=whu, height=100]:117800 Person [name=nlp, height=1000]:110170 Person [name=lcz, height=10000]:116979 --------- 修改以後的:Person [name=nlp mode, height=1000]:-1258866055 --------- Person [name=lcz, height=10000]:p3:116979 Person [name=nlp mode, height=1000]:p2:-1258866055 已經發生改變true Person [name=whu, height=100]:p1:117800 沒有改變true --------- 最後的結果null
從輸出咱們能夠知道, key 更新後 hashCode 確實更新了。並且 HashMap 裏面的對象就是咱們原來的對象。最後的結果是null。 測試
咱們來看一下 HashMap 的get方法源代碼: this
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
能夠看到先取得了一個table,這個table其實是個數組。而後在table裏面找對應 key 的value。找的標準就是hash等於傳入參數的hash, 而且知足另外兩個條件之一:k = e.key,也就是說他們是同一個對象,或者傳入的 key 的equal目標的 key 。咱們的問題出在那個hash(key.hashCode()),能夠看到 HashMap 在存儲元素時是把 key 的 hashCode 再作了一次hash。獲得的hash將最終做爲元素存儲位置的依據。對應到咱們的狀況:第一次存儲時,hash函數採用key.hashCode做爲參數獲得了一個值,而後根據這個值把元素存到了某個位置。 code
當咱們再去取元素的時候,key.hashCode的值已經出現了變化,因此這裏的hash函數結果也發生了變化,因此當它嘗試去得到這個 key 的存儲位置時就不能獲得正確的值,致使最終找不到目標元素。要想能正確返回,很簡單,把Person類的 hashCode 方法改一下,讓它的 hashCode 不依賴咱們要修改的屬性,但實際開發中確定不能這麼幹,咱們老是但願當兩個對象的屬性不徹底相同時能返回不一樣的 hashCode 值。因此結論就是當把對象放到 HashMap 後,不要去修改 key 的屬性。 對象
以上都是很基礎的東西,但或許咱們不少時候都沒注意到,瞭解這些基礎能夠避免一些很詭異的bug。純屬拋磚引玉,若有謬誤請海涵和指出。