HashMap的key可以是可变的对象吗???

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體:   |    |    |  超大  

大家都知道,HashMap的是key-value(键值对)组成的,这个key既可以是基本数据类型对象,如Integer,Float,同时也可以是自己编写的对象,那么问题来了,这个作为key的对象是否能够改变呢?或者说key能否是一个可变的对象?如果可以该HashMap会怎么样?

可变对象

可变对象是指创建后自身状态能改变的对象。换句话说,可变对象是该对象在创建后它的哈希值(由类的hashCode()方法可以得出哈希值)可能被改变。

为了能直观的看出哈希值的改变,下面编写了一个类,同时重写了该类的hashCode()方法和它的equals()方法【至于为什么要重写equals方法可以看博客:http://www.cnblogs.com/0201zcr/p/4769108.html】,在查找和添加(put方法)的时候都会用到equals方法。

在下面的代码中,对象MutableKey的键在创建时变量 i=10 j=20,哈希值是1291。

然后我们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。现在Key的哈希值已经变成1931。

显然,这个对象的键在创建后发生了改变。所以类MutableKey是可变的。

让我们看看下面的示例代码:

publicclassMutableKey {

PRivateinti;

privateintj;

publicMutableKey(inti,intj) {

this.i =i;

this.j =j;

}

publicfinalintgetI() {

returni;

}

publicfinalvoidsetI(inti) {

this.i =i;

}

publicfinalintgetJ() {

returnj;

}

publicfinalvoidsetJ(intj) {

this.j =j;

}

@Override

publicinthashCode() {

finalintprime = 31;

intresult = 1;

result= prime * result +i;

result= prime * result +j;

returnresult;

}

@Override

publicbooleanequals(Object obj) {

if(this==obj) {

returntrue;

}

if(obj ==null) {

returnfalse;

}

if(!(objinstanceofMutableKey)) {

returnfalse;

}

MutableKey other=(MutableKey) obj;

if(i !=other.i) {

returnfalse;

}

if(j !=other.j) {

returnfalse;

}

returntrue;

}

}

测试:

publicclassMutableDemo {publicstaticvoidmain(String[] args) {//Object createdMutableKey key =newMutableKey(10, 20);

System.out.println("Hash code: " +key.hashCode());//Object State is changed after object creation.key.setI(30);

key.setJ(40);

System.out.println("Hash code: " +key.hashCode());

}

}

结果:

Hash code: 1291

Hash code: 1931

只要MutableKey 对象的成员变量i或者j改变了,那么该对象的哈希值改变了,所以该对象是一个可变的对象。

HashMap如何存储键值对

HashMap底层是使用Entry对象数组存储的,而Entry是一个单项的链表。当调用一个put()方法将一个键值对添加进来是,先使用hash()函数获取该对象的hash值,然后调用indexFor方法查找到该对象在数组中应该存储的下标,假如该位置为空,就将value值插入,如果该下标出不为空,则要遍历该下标上面的对象,使用equals方法进行判断,如果遇到equals()方法返回真的则进行替换,否则将其插入,源码详解可看:http://www.cnblogs.com/0201zcr/p/4769108.html。

查找时只需要查询通过key值获取获取hash值,然后找到其下标,遍历该下标下面的Entry对象即可查找到value。【具体看下面源码及其解释】

在HashMap中使用可变对象作为Key带来的问题如果HashMap Key的哈希值在存储键值对后发生改变,Map可能再也查找不到这个Entry了。

publicV get(Object key)

{//如果 key 是 null,调用 getForNullKey 取出对应的 valueif(key ==null)returngetForNullKey();//根据该 key 的 hashCode 值计算它的 hash 码inthash =hash(key.hashCode());//直接取出 table 数组中指定索引处的值,for(Entry<K,V> e =table[indexFor(hash, table.length)];

e!=null;//搜索该 Entry 链的下一个 Entre = e.next)//①{

Object k;//如果该 Entry 的 key 与被搜索 key 相同if(e.hash == hash && ((k = e.key) ==key||key.equals(k)))returne.value;}returnnull;

}

上面是HashMap的get()方法源码,通过上面我们可以知道,如果 HashMap 的每个 bucket 里只有一个 Entry时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。

同时我们也看到,判断是否找到该对象,我们还需要判断他的哈希值是否相同,假如哈希值不相同,根本就找不到我们要找的值。

如果Key对象是可变的,那么Key的哈希值就可能改变。在HashMap中可变对象作为Key会造成数据丢失。

下面的例子将会向你展示HashMap中有可变对象作为Key带来的问题。

importjava.util.HashMap;importjava.util.Map;publicclassMutableDemo1 {publicstaticvoidmain(String[] args) {//HashMapMap<MutableKey, String> map =newHashMap<>();//Object createdMutableKey key =newMutableKey(10, 20);//Insert entry.map.put(key, "Robin");//This line will print 'Robin'System.out.println(map.get(key));//Object State is changed after object creation.//i.e. Object hash code will be changed.key.setI(30);//This line will print null as Map would be unable to retrieve the//entry.System.out.println(map.get(key));

}

}

输出:

Robinnull

如何解决

在HashMap中使用不可变对象。在HashMap中,使用String、Integer等不可变类型用作Key是非常明智的。

我们也能定义属于自己的不可变类。

如果可变对象在HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。

在下面的Employee示例类中,哈希值是用实例变量id来计算的。一旦Employee的对象被创建,id的值就不能再改变。只有name可以改变,但name不能用来计算哈希值。所以,一旦Employee对象被创建,它的哈希值不会改变。所以Employee在HashMap中用作Key是安全的。

importjava.util.HashMap;importjava.util.Map;publicclassMutableSafeKeyDemo {publicstaticvoidmain(String[] args) {

Employee emp=newEmployee(2);

emp.setName("Robin");//Put object in HashMap.Map<Employee, String> map =newHashMap<>();

map.put(emp,"Showbasky");

System.out.println(map.get(emp));//Change Employee name. Change in 'name' has no effect//on hash code.emp.setName("Lily");

System.out.println(map.get(emp));

}

}classEmployee {//It is specified while object creation.//Cannot be changed once object is created. No setter for this field.privateintid;privateString name;publicEmployee(finalintid) {this.id =id;

}publicfinalString getName() {returnname;

}publicfinalvoidsetName(finalString name) {this.name =name;

}publicintgetId() {returnid;

}//Hash code depends only on 'id' which cannot be//changed once object is created. So hash code will not change//on object's state change@OverridepublicinthashCode() {finalintprime = 31;intresult = 1;

result= prime * result +id;returnresult;

}

@Overridepublicbooleanequals(Object obj) {if(this==obj)returntrue;if(obj ==null)returnfalse;if(getClass() !=obj.getClass())returnfalse;

Employee other=(Employee) obj;if(id !=other.id)returnfalse;returntrue;

}

}

输出

Showbasky

Showbasky

致谢:感谢您的耐心阅读!

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航