• 一个关于自定义类型作为HashMap的key的问题


    在之前的项目需要用到以自定义类型作为HashMap的key,遇到一个问题:如果修改了已经存储在HashMap中的实例,会发生什么情况呢?用一段代码来试验:

    import java.util.HashMap;
    import java.util.Map;
    
    public class TestHashMap {
    
        public static void main(String[] args) {
            testObjAsKey();
        }
    
        private static void testObjAsKey() {
            class Person {
                public String familyName;
                public String givenName;
    
                public Person(String familyName, String givenName) {
                    this.familyName = familyName;
                    this.givenName = givenName;
                }
    
                @Override
                public int hashCode() {
                    final int prime = 31;
                    int result = 1;
                    result = prime * result
                            + ((familyName == null) ? 0 : familyName.hashCode());
                    result = prime * result
                            + ((givenName == null) ? 0 : givenName.hashCode());
                    return result;
                }
    
                @Override
                public boolean equals(Object obj) {
                    if (this == obj) {
                        return true;
                    }
                    if (obj == null) {
                        return false;
                    }
                    if (!(obj instanceof Person)) {
                        return false;
                    }
                    Person other = (Person) obj;
                    if (familyName == null) {
                        if (other.familyName != null) {
                            return false;
                        }
                    } else if (!familyName.equals(other.familyName)) {
                        return false;
                    }
                    if (givenName == null) {
                        if (other.givenName != null) {
                            return false;
                        }
                    } else if (!givenName.equals(other.givenName)) {
                        return false;
                    }
                    return true;
                }
    
                @Override
                public String toString() {
                    return "Person(" + familyName + ", " + givenName + ")";
                }
    
            }
    
            Map<Person, Integer> map = new HashMap<Person, Integer>();
            Person person1 = new Person("zhang", "san");
    
            map.put(person1, 1);
            System.out.println("Value of " + person1 + " is " + map.get(person1));
    
            person1.givenName = "si";
            System.out.println("'zhang san' is changed to 'zhang si'");
            System.out.println("Value of 'zhang san' is " + map.get(new Person("zhang", "san")));
            System.out.println("Value of 'zhang si' is " + map.get(new Person("zhang", "si")));
            System.out.println("Value of `person1` is " + map.get(person1));
    
        }
    
    }

    程序的输出是什么?答案见下

    Value of Person(zhang, san) is 1
    'zhang san' is changed to 'zhang si'
    Value of 'zhang san' is null
    Value of 'zhang si' is null
    Value of `person1` is null

    为什么这样呢?这要从HashMap的实现进行分析。HashMap使用一个Entry数组保存内部的元素(Entry是用来保存<key, value="">对的类型)。数组的每个slot保存一个链表的头指针,这个链表内的元素都是hashCode相同的entry

    Entry[] tables
        +---+
        | 0 | -> entry_0_0 -> entry_0_1 -> null
        +---+
        | 1 | -> null
        +---+
        |   |
    
         ...
    
        |n-1| -> entry_n-1_0 -> null
        +---+

    HashMapput()get()方法基本原理如下: - put一个元素的时候,根据key的hashCode()方法计算出hash值,进而算出相应的数组下标,然后将这个新的entry加入到链表中。 - get一个key的时候,根据key的hash值找到相应的数组下标,然后遍历这个链表,并查找和当前key相等的entry。判断两个元素是否相等时使用使用equals()方法

    上面的例子中,在put操作之后,map内部的存储是这样的(假设这个元素被存储在了第i个slot):

        |   | -> null
        +---+
        | i | -> entry<person1("zhang", "san"), 1> -> null
        +---+
        |   | -> null

    注意,当我们修改了person1的时候,并没有修改它存储在map中的位置。也就是说,修改之后的map的内部存储是这样的:

        |   | -> null
        +---+
        | i | -> entry<person1("zhang", "si"), 1> -> null
        +---+
        |   | -> null

    person1仍然存储在第i个slot里。

    get(new Person("zhang", "si"))的时候,HashMap将先根据hash值计算它应该位于第几个slot,比如是j。由于根据Person("zhang", "san")计算出来的下标是i,i和j很可能是不相同的,那么第j个slot是空的(因为之前只put了一个元素且位于第i个slot),因此get()方法将返回null

    get(new Person("zhang", "san"))的时候,HashMap将计算它应该位于第i个slot,然后在这个链表中查找。这个链表中存储了一个元素,但是当前的key是("zhang", "si"),使用equals()方法判断这两个key是否相等时将返回false,也就是说,HashMap在第i个slot所维护的链表中没有找到和当前key相等的元素,因此get()方法将返回null

    get(person1)的时候,因为person1现在的值是("zhang", "si"),所以和get(new Person("zhang", "si"))的情况是完全一样的。

    总结一下,上面的分析说明,如果不小心改变了已经存储在HashMap中的key值,那么将引起潜在的错误。显然,避免这个问题比较好的方法是,如果打算将自己定义的一种数据类型作为key,那么将这个类型设计成不可变的(immutable)。比如IntegerString等都是不可变的。

  • 相关阅读:
    element-ui 中 el-table 根据scope.row行数据变化动态显示行内控件
    vue.js 父组件主动获取子组件的数据和方法、子组件主动获取父组件的数据和方法
    把json1赋值给json2,修改json2的属性,json1的属性也一起变化
    win10下当前目录右键添加CMD快捷方式
    element-ui
    vscode 头部注释插件
    IE浏览器new Date()带参返回NaN解决方法
    常用css
    使用DataGridView控件显示数据
    第四章 ADO.NET
  • 原文地址:https://www.cnblogs.com/aquastone/p/obj-as-hashmap-key.html
Copyright © 2020-2023  润新知