一个关于自定义类型作为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等都是不可变的。

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

时间: 2024-10-07 00:26:06

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

Java用自定义的类型作为HashMap的key

??需要重写hashCode()和equals()方法才可以实现自定义键在HashMap中的查找. public class PhoneNumber { private int prefix; //区号 private int phoneNumber; //电话号 public PhoneNumber(int prefix, int phoneNumber) { this.prefix = prefix; this.phoneNumber = phoneNumber; } } import ja

java自定义类型 作为HashMap中的Key值 (Pair&lt;V,K&gt;为例)

由于是自定义类型,所以HashMap中的equals()函数和hashCode()函数都需要自定义覆盖. 不然内容相同的对象对应的hashCode会不同,无法发挥算法的正常功能,覆盖equals函数,应该就相当于c++重载==运算符来保证能判断是否相等.只不过java没有自定义重载运算符这个功能的,需要进行函数覆盖. equals的函数原型是 boolean equals(Object o);注意括号内.hashCode的函数原型就是int hashCode(); 先看一段代码: import

Java自定义类型作为HasMap的key的查找

最近常常会用到一些之前看过却没有实际去实现的小细节,深有感慨(掌握一门技术绝不是看一遍就够了,一遍远远不够,远远不够........), 言归正传,先直接上代码 Attributeresult 1 public class Attributeresult { 2 String value; 3 String result; 4 5 public Attributeresult() { 6 7 } 8 9 public Attributeresult(String value, String re

priority_queue里面自定义类型的使用。

1 #include<iostream> 2 #include<queue> 3 using namespace std; 4 struct tree{ 5 int num; 6 string s; 7 tree(int x,string zfc) 8 { 9 num=x; 10 s=zfc; 11 } 12 friend bool operator>(const tree &a,const tree &b) 13 { 14 return a.num>b

map以自定义类型当Key

关于map的定义: template < class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key,T> > > class map; 第一个template参数被当做元素的key,第二个template参数被当作元素的value.Map的元素型别Key和T,必须满足以下两个条件:1.key/value必须具备assignable(可赋值

Java 将自定义的对象作为HashMap的key

需要继承Map的equals函数和hashCode函数 package com.category; import java.util.HashMap; public class GenCategoryLevelData { private static HashMap<Category, Integer> categoryLevel = new HashMap<Category, Integer>(); /** * @param args */ public static void

HashMap 中 Key 类型的选择

什么对象可以作为HashMap的key值? 从HashMap的语法上来讲,一切对象都可以作为Key值.如:Integer.Long.String.Object等.但是在实际工作中,最常用的使用String作为Key值. 原因如下: 1.使用Object作为Key值的时候,如Class Person (里面包含,姓名,年龄,性别,电话等属性)作为Key.当Person类中的属性改变时,导致hashCode的值也发生变化,变化后,map.get(key)因为hashCode值的变化,而无法找到之前保

【Spring】利用spring的JdbcTemplate查询返回结果映射到自定义类型

// org.springframework.jdbc.core.JdbcTemplate 中的查询方法基本都有支持参数RowMapper<T> rowMapper的重载方法.下面只是随便举例2个,还有很多 public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException { ... }; public <T>

Hadoop日记Day13---使用hadoop自定义类型处理手机上网日志

测试数据的下载地址为:http://pan.baidu.com/s/1gdgSn6r 一.文件分析 首先可以用文本编辑器打开一个HTTP_20130313143750.dat的二进制文件,这个文件的内容是我们的手机日志,文件的内容已经经过了优化,格式比较规整,便于学习研究,感兴趣的读者可以尝试一下. 我从中截取文件中的一行记录内容进行分析: 1363157985066     13726230503    00-FD-07-A4-72-B8:CMCC    120.196.100.82    i