而當你使用HashMap存儲鍵值對時,有可能會遇到一個疑問。
下面舉一個我遇到的問題:
Person是一個實體類(已經重寫了equals和hashCode方法) ,這裡的HashMap是官方的HashMap
= new com.mj.set.Person(12,1.67f,"jack");
Person p2 = new com.mj.set.Person(12,1.67f,"jack");
java.util.Map<Object, Integer> map2 = new java.util.HashMap<>();
map2.put(p1,1);
map2.put(p2,2);
System.out.println(map2.size());
System.out.println(map2.containsKey(p1));
當你在主函數執行這段代碼時,會發現結果為1,true。
而你可能會有這樣的疑惑:你不是已經用p2覆寫了p1嗎,為什麼map2中還是包含p1?
作出解釋:
你翻閱Java官方的HashMap你會發現,那個containsKey(K key)方法是通過傳入hash(key);,以及key查找node是否為空,如果為空,則傳回false, 否則傳回true。
getNode方法
/**
* 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;
}
/**
* Returns {@code true} if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return {@code true} if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
而你細看上面建立的兩個對象你會發現,兩個對象的成員變量的内容都是一緻的
Person類
package com.mj.set;
public class Person implements Comparable<Person> {
private int age; // 10 20
private float height; // 1.55 1.67
private String name; // "jack" "rose"
public Person(int age, float height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
@Override
/**
* 用來比較2個對象是否相等
*/
public boolean equals(Object obj) {
// 記憶體位址
if (this == obj) return true;
if (obj == null || obj.getClass() != getClass()) return false;
// if (obj == null || !(obj instanceof Person)) return false;
// 比較成員變量
Person person = (Person) obj;
return person.age == age
&& person.height == height
&& (person.name == null ? name == null : person.name.equals(name));
}
@Override
public int hashCode() {
int hashCode = Integer.hashCode(age);
hashCode = hashCode * 31 + Float.hashCode(height);
hashCode = hashCode * 31 + (name != null ? name.hashCode() : 0);
return hashCode;
}
@Override
public int compareTo(Person o) {
return age - o.age;
}
}
既然内容都一緻,這就意味着hashCode的傳回值都一緻,則hash函數的結果也一緻:
Java官方的hash函數
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
而當你檢視官方的getNode方法時,你會發現有這麼幾句代碼
即當hash()的傳回值相同以及key.equals(k)都為true時,才傳回找到的對應的節點,而之前已經說過p1與p2的hash值是一定相等的,
再看equals方法
/**
* 用來比較2個對象是否相等
*/
public boolean equals(Object obj) {
// 記憶體位址
if (this == obj) return true;
if (obj == null || obj.getClass() != getClass()) return false;
// if (obj == null || !(obj instanceof Person)) return false;
// 比較成員變量
Person person = (Person) obj;
return person.age == age
&& person.height == height
&& (person.name == null ? name == null : person.name.equals(name));
}
由于p1和p2成員變量的内容一緻,則euqals方法一定傳回true