在HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么?
我们办公室几个人答案都不一致,有的说返回null,有的说能正常返回value。但不论答案是什么都没有确凿的理由。我觉得这个问题挺有意思的,就写了代码测试。结果是返回null。需要说明的是我们自定义的类重写了 hashCode 方法。我想这个结果还是有点意外的,因为我们知道 HashMap 存放的是引用类型,我们在外面把 key 更新了,那也就是说 HashMap 里面的 key 也更新了,也就是这个 key 的 hashCode 返回值也会发生变化。这个时候 key 的 hashCode 和 HashMap 对于元素的 hashCode 肯定一样,equals也肯定返回true,因为本来就是同一个对象,那为什么不能返回正确的值呢?
测试案例
这里有 2 个案例,一个是 Person 类,还有一个是 Student 类,我们来验证下以上的观点(附带结论):
- 修改了对象属性是否会改变它的 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;
}
输出结果
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;
}