一个关于自定义类型作为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
+---+
HashMap的put()和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)。比如Integer,String等都是不可变的。
一个关于自定义类型作为HashMap的key的问题的更多相关文章
- Java用自定义的类型作为HashMap的key
需要重写hashCode()和equals()方法才可以实现自定义键在HashMap中的查找. public class PhoneNumber { private int prefix; //区 ...
- java自定义类型 作为HashMap中的Key值 (Pair<V,K>为例)
由于是自定义类型,所以HashMap中的equals()方法和hashCode()方法都需要自定义覆盖. 不然内容相同的对象对应的hashCode会不同,无法发挥算法的正常功能,覆盖equals方法, ...
- 为什么不建议使用自定义Object作为HashMap的key?
此前部门内的一个线上系统上线后内存一路飙高.一段时间后直接占满.协助开发人员去分析定位,发现内存中某个Object的量远远超出了预期的范围,很明显出现内存泄漏了. 结合代码分析发现,泄漏的这个对象,主 ...
- JAVA - 如果hashMap的key是一个自定义的类,怎么办?
JAVA - 如果hashMap的key是一个自定义的类,怎么办? 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals().
- HashMap存储自定义类型键值和LinkedHashMap集合
HashMap存储自定义类型键值 1.当给HashMap中存放自定义对象时,如果自定义对象是键存在,保证键唯一,必须复写对象的hashCode和equals方法. 2.如果要保证map中存放的key和 ...
- 关于set或map的key使用自定义类型的问题
我们都知道set或map的key使用自定义类型时必须重载<关系运算符 但是,还有一个条件,所调用重载的小于操作符,使用的对象必须是const 而对象调用的方法也必须是const的 1 #incl ...
- map以自定义类型当Key
关于map的定义: template < class Key, class T, class Compare = less<Key>, class Allocator = alloc ...
- [Java]如何为一个自定义类型的List排序。
好吧,三年了,又重拾我的博客了,是因为啥呢,哈哈哈.今天被问到一个题目,当场答不出来,动手动的少了,再此记录下来. Q:有一个MyObject类型的List,MyObject定义如下: class M ...
- std::map自定义类型key
故事背景:最近的需求需要把一个结构体struct作为map的key,时间time作为value,定义:std::map<struct, time> _mapTest; 技术调研:众所周知, ...
随机推荐
- Spring 计划
3.0----------------------------------------------------- SCRUM 流程的步骤2: Spring 计划 1. 确保product backlo ...
- Access数据库中Sum函数返回空值(Null)时如何设置为0
在完成一个Access表中数据统计时,需要统计指定字段的和,使用到了Sum函数,但统计时发现,指定条件查询统计时有可能返回空值(Null),导致对应字段显示为空白,正常应显示为0.基本思路是在获取记录 ...
- Java语言中,类所拥有的“孩子”,他们的关系是怎样的
学习了一本有关Java的书.初步了解了一些面向对象的内容. java是由一个个的类组成的,这些类组成了java程序.类之下有他的孩子,这四个孩子分别是: 成员变量:就相当于一个个的变量,他由stati ...
- 解决qt程序的链接阶段出现 undefined reference 错误
错误的原因是我使用到了 QT Widgets 模块中的东西,但是makefile的链接的参数中没有 widgets.其实官网上提到了这个: http://doc.qt.io/qt-5/qtwidget ...
- cxf(3.1.1) 异常Caused by: java.io.FileNotFoundException: class path resource [META-INF/cxf/cxf-extension-soap.xml]
Caused by: java.io.FileNotFoundException: class path resource [META-INF/cxf/cxf-extension-soap.xml] ...
- Java运行环境的配置
Make sure you do not use the trailing semicolon: This will not work: set JAVA_HOME=C:\Program Files ...
- animation 的属性一共有 6 个值,详细介绍在此
animation 属性是一个简写属性,用于设置六个动画属性: animation-name animation-duration animation-timing-function animatio ...
- viewpager实现酷炫侧滑demo
晚上叫外卖,打开饿了么,发现推了一个版本,更新以后,点开了个鸡腿,哇,交互炫炸了. 不过还是有槽点.我是无意中才发现可以左右滑动的.这...你不告诉我,我怎么知道左右可以滑. https://gith ...
- web工程目录结构
/WEB-INF/web.xml Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则. /WEB-INF/classes/包含了站点所有用的 class 文件,包括 ser ...
- 关于css的一些知识点整理
一.标签的类型: 行内:span.a.b.i.strong.em. 1.共处一行 2.不支持设置宽高 display:block; 转换成块 块:h1-h6 p div ul ol 1. ...