Java如何根據(jù)key值修改Hashmap中的value值
根據(jù)key值修改Hashmap的value值
如果原來map中沒有key,會創(chuàng)建,如果原來有key,會使用value 覆蓋掉原來的值
map.put(key,value);
這個實現(xiàn)對原值加一(前提是有這個key)
map.put(key,map.get(key)+1);
以下可以獲取key對應(yīng)的value,如果沒有可以返回默認(rèn)的value
map.getOrDefault(key,value);
HashMap的key更改后能否正確獲取value?
在HashMap 中存放的一系列鍵值對,其中鍵為某個我們自定義的類型。放入 HashMap 后,我們在外部把某一個 key 的屬性進(jìn)行更改,然后我們再用這個 key 從 HashMap 里取出元素,這時候 HashMap 會返回什么?
我們辦公室?guī)讉€人答案都不一致,有的說返回null,有的說能正常返回value。但不論答案是什么都沒有確鑿的理由。我覺得這個問題挺有意思的,就寫了代碼測試。結(jié)果是返回null。需要說明的是我們自定義的類重寫了 hashCode 方法。我想這個結(jié)果還是有點意外的,因為我們知道 HashMap 存放的是引用類型,我們在外面把 key 更新了,那也就是說 HashMap 里面的 key 也更新了,也就是這個 key 的 hashCode 返回值也會發(fā)生變化。這個時候 key 的 hashCode 和 HashMap 對于元素的 hashCode 肯定一樣,equals也肯定返回true,因為本來就是同一個對象,那為什么不能返回正確的值呢?
測試案例
這里有 2 個案例,一個是 Person 類,還有一個是 Student 類,我們來驗證下以上的觀點(附帶結(jié)論):
- 修改了對象屬性是否會改變它的 hashcode => 是的
- 在 HashMap 里存取的時候是否會受到修改屬性影響取值 => 取值為 null
package tech.luxsun.interview.luxinterviewstarter.collection; ? import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.HashMap; ? /** ?* @author Lux Sun ?* @date 2021/4/22 ?*/ public class MapDemo0 { ? ? ? public static void main(String[] args) { ? ? ? ? HashMap<Object, Object> map = new HashMap<>(); ? ? ? ? ? // Person Case ? ? ? ? Person p = new Person("Bob", 12); ? ? ? ? map.put(p, "person"); ? ? ? ? System.out.println(p.hashCode()); ? ? ? ? System.out.println(map.get(p)); ? ? ? ? ? p.setAge(13); ? ? ? ? System.out.println(p.hashCode()); ? ? ? ? System.out.println(map.get(p)); ? ? ? ? ? // Student Case ? ? ? ? Student stu = new Student("Bob", 12); ? ? ? ? map.put(stu, "student"); ? ? ? ? System.out.println(stu.hashCode()); ? ? ? ? System.out.println(map.get(stu)); ? ? ? ? ? stu.setAge(13); ? ? ? ? System.out.println(stu.hashCode()); ? ? ? ? System.out.println(map.get(stu)); ? ? } } ? @Data @AllArgsConstructor @NoArgsConstructor class Person { ? ? private String name; ? ? private Integer age; ? ? ? public int hashCode() { ? ? ? ? return 123456; ? ? } } ? @Data @AllArgsConstructor @NoArgsConstructor class Student { ? ? private String name; ? ? private Integer age; }
輸出結(jié)果
123456
person
123456
person
71154
student
71213
null
源碼
hashCode 源碼
public int hashCode() { ? ? int PRIME = true; ? ? int result = 1; ? ? Object $age = this.getAge(); ? ? int result = result * 59 + ($age == null ? 43 : $age.hashCode()); ? ? Object $name = this.getName(); ? ? result = result * 59 + ($name == null ? 43 : $name.hashCode()); ? ? return result; }
map.get 源碼
/** ?* Returns the value to which the specified key is mapped, ?* or {@code null} if this map contains no mapping for the key. ?* ?* <p>More formally, if this map contains a mapping from a key ?* {@code k} to a value {@code v} such that {@code (key==null ? k==null : ?* key.equals(k))}, then this method returns {@code v}; otherwise ?* it returns {@code null}. ?(There can be at most one such mapping.) ?* ?* <p>A return value of {@code null} does not <i>necessarily</i> ?* indicate that the map contains no mapping for the key; it's also ?* possible that the map explicitly maps the key to {@code null}. ?* The {@link #containsKey containsKey} operation may be used to ?* distinguish these two cases. ?* ?* @see #put(Object, Object) ?*/ public V get(Object key) { ? ? Node<K,V> e; ? ? return (e = getNode(hash(key), key)) == null ? null : e.value; } ? ? /** ?* Computes key.hashCode() and spreads (XORs) higher bits of hash ?* to lower. ?Because the table uses power-of-two masking, sets of ?* hashes that vary only in bits above the current mask will ?* always collide. (Among known examples are sets of Float keys ?* holding consecutive whole numbers in small tables.) ?So we ?* apply a transform that spreads the impact of higher bits ?* downward. There is a tradeoff between speed, utility, and ?* quality of bit-spreading. Because many common sets of hashes ?* are already reasonably distributed (so don't benefit from ?* spreading), and because we use trees to handle large sets of ?* collisions in bins, we just XOR some shifted bits in the ?* cheapest possible way to reduce systematic lossage, as well as ?* to incorporate impact of the highest bits that would otherwise ?* never be used in index calculations because of table bounds. ?*/ static final int hash(Object key) { ? ? int h; ? ? return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ? /** ?* Implements Map.get and related methods ?* ?* @param hash hash for key ?* @param key the key ?* @return the node, or null if none ?*/ final Node<K,V> getNode(int hash, Object key) { ? ? Node<K,V>[] tab; Node<K,V> first, e; int n; K k; ? ? if ((tab = table) != null && (n = tab.length) > 0 && ? ? ? ? (first = tab[(n - 1) & hash]) != null) { ? ? ? ? if (first.hash == hash && // always check first node ? ? ? ? ? ? ((k = first.key) == key || (key != null && key.equals(k)))) ? ? ? ? ? ? return first; ? ? ? ? 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; }
總之
可以看到先取得了一個table,這個table實際上是個數(shù)組。然后在table里面找對應(yīng) key 的value。找的標(biāo)準(zhǔn)就是hash等于傳入?yún)?shù)的hash, 并且滿足另外兩個條件之一:k = e.key,也就是說他們是同一個對象,或者傳入的 key 的equal目標(biāo)的 key 。我們的問題出在那個hash(key.hashCode()),可以看到 HashMap 在存儲元素時是把 key 的 hashCode 再做了一次hash。得到的hash將最終作為元素存儲位置的依據(jù)。對應(yīng)到我們的情況:第一次存儲時,hash函數(shù)采用key.hashCode作為參數(shù)得到了一個值,然后根據(jù)這個值把元素存到了某個位置。
當(dāng)我們再去取元素的時候,key.hashCode的值已經(jīng)出現(xiàn)了變化,所以這里的hash函數(shù)結(jié)果也發(fā)生了變化,所以當(dāng)它嘗試去獲得這個 key 的存儲位置時就不能得到正確的值,導(dǎo)致最終找不到目標(biāo)元素。要想能正確返回,很簡單,把Person類的 hashCode 方法改一下,讓它的 hashCode 不依賴我們要修改的屬性,但實際開發(fā)中肯定不能這么干,我們總是希望當(dāng)兩個對象的屬性不完全相同時能返回不同的 hashCode 值。
所以結(jié)論就是當(dāng)把對象放到 HashMap 后,不要去修改 key 的屬性,除非你重寫了該實體類的 hashCode 方法不受屬性限制。
最后
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot + Mybatis增刪改查實戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于SpringBoot + Mybatis增刪改查的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05SpringBoot整合mysql、postgres及sqlserver實現(xiàn)多數(shù)據(jù)源配置實戰(zhàn)案例
在工作中業(yè)務(wù)的發(fā)展或業(yè)務(wù)數(shù)據(jù)隔離的場景下,通常需要一個項目中引入多個數(shù)據(jù)源,但SpringBoot默認(rèn)的自動化配置是單數(shù)據(jù)源的,這篇文章主要給大家介紹了關(guān)于SpringBoot整合mysql、postgres及sqlserver實現(xiàn)多數(shù)據(jù)源配置的相關(guān)資料,需要的朋友可以參考下2023-12-12jar命令修改jar包中的application.yml配置文件
本文主要介紹了jar命令修改jar包中的application.yml配置文件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08Struts2學(xué)習(xí)筆記(2)-路徑問題解決
本文主要介紹Struts2的路徑問題,盡量不要使用相對路徑,使用相對路徑會讓路徑問題變得很繁瑣很麻煩,推薦使用絕對路徑,希望能給大家做一個參考。2016-06-06Maven中dependency和plugins的繼承與約束
這篇文章主要介紹了Maven中dependency和plugins的繼承與約束,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-12-12Spring中事務(wù)用法示例及實現(xiàn)原理詳解
這篇文章主要給大家介紹了關(guān)于Spring中事務(wù)用法示例及實現(xiàn)原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11