而当你使用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方法时,你会发现有这么几句代码

学习Java哈希表HashMap时可能会遇到的小困惑_hashcode


即当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

而你查找p1时,通过p1创建的节点已经存储在了map2中,那么一定会找到这个node结点,即getNode方法会返回找到的节点。则containsKey(p1)必然返回true

另外,HashMap的遍历结果是无序的。