欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-25 22:07:10
...

前言:hashmap是数据+链表的结构,决定它的元素在内存的位置的是key的hashcode值,当然该元素的位置的最终确定还取决于该hashmap中是否已经有相同hashcode值的其他元素,我们来看源码:
HashMap中的key为什么不能为可变对象(除非重写它的hashcode方法和equals方法)
修改一个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<Person, String>();
        map.put(p1, "CSDN");
        map.put(p2, "CSDN");

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

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

        p1.setId(3);
        map.put(p1, "C博客");
        Iterator<Map.Entry<Integer, Integer>> entries1 = map.entrySet().iterator();  
        while (entries1.hasNext()) {  
            Map.Entry<Integer, Integer> 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值。