咱们公司招人喜欢问算法题和一些基础知识。今天咱们一个面试官在面试候选人以前在办公室对咱们说他准备问一个这样的问题: 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。纯属抛砖引玉,若有谬误请海涵和指出。