HashMap中的key为什么不能为可变对象(除非重写它的hashcode方法和equals方法)

前言:hashmap是数据+链表的结构,决定它的元素在内存的位置的是key的hashcode值,当然该元素的位置的最终确定还取决于该hashmap中是否已经有相同hashcode值的其他元素,我们来看源码:
HashMap中的key为什么不能为可变对象(除非重写它的hashcode方法和equals方法)_第1张图片
修改一个key-value键值对,要经过这五个步骤:
第一步:调用key.hashCode()获取key的hashcode值;
第二步:调用hash(),计算hash值,此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突

static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

第三步:调用indexFor(int h, int length),获取该元素在数组中的索引位置:

static int indexFor(int h, int length) {
        return h & (length-1);
    }

第四步:获取第三步得到的索引值,取到对应的数据,如果该索引处的数据不为空,则表示那个位置已经有了一个元素了,接下来就要比较看put()进来的这个元素是否等于该位置上的原有元素了,比较的方法就是4,5这两个条件:

由4和5我们可以看到:
比较的第一个条件就是:e.hash == hash,也就是说比较这两个元素的hash值是否一致,而hash值就取决于key的hashcode;
比较的第二个条件就是:((k = e.key) == key || key.equals(k)),也就是比较key是否相等或者equals是否一致。

如果,如果我们的key是一个可变对象,那我们改变它的元素就注定会改变它的hashcode值(不会变化的情况太少太少),来看代码:

package zm.demo;

public class Person {
    private int id;
    private String name;
    private int age;

    public Person() {
    }

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public int hashCode() {
        return id;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {

        return "ID: " + id + " name: " + name + " age:" + age + " ";
    }


}
package zm.demo;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Demo {
    public static void main(String[] args) {
        Person p1 = new Person(1, "张三", 20);
        Person p2 = new Person(2, "李四", 22);
        Map map = new HashMap();
        map.put(p1, "CSDN");
        map.put(p2, "CSDN");

        Iterator.Entry> entries = map.entrySet().iterator();  
        while (entries.hasNext()) {  
            Map.Entry entry = entries.next();  
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
        } 

        System.out.println("###############################################");

        p1.setId(3);
        map.put(p1, "C博客");
        Iterator.Entry> entries1 = map.entrySet().iterator();  
        while (entries1.hasNext()) {  
            Map.Entry entry = entries1.next();  
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
        }  

    }


}

结果:

Key = ID: 1 name: 张三 age:20 , Value = CSDN
Key = ID: 2 name: 李四 age:22 , Value = CSDN
###############################################
Key = ID: 3 name: 张三 age:20 , Value = CSDN
Key = ID: 2 name: 李四 age:22 , Value = CSDN
Key = ID: 3 name: 张三 age:20 , Value = C博客

很奇怪的现象出来了,改变了p1的属性id(重写了hashcode方法,为了方便展示,就将hashcode设为id值),但是还是同样的对象,为什么结果却是map里存了3个值呢???

答案:正是因为改变了对象的属性值,导致对象的hashcode发生改变,再次put()的时候,虽然是同一个对象,但是在put()里第3步的时候获取的索引值会不一样,不能匹配到原来的对象,更不用说第4步了,从而形成了一个新的元素插入进去。同样的,调用map.get(p1)获取的也不会是”CSDN”而是”C博客”

结论:如果一定要使用可变对象作为key,就需要保证该对象的属性发生改变时,不会改变对象的hashcode值。

你可能感兴趣的:(java,hashmap,hash,hashcode)