大家都知道,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是可变的。

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

public class MutableKey {private int i;private int j;public MutableKey(int i, int j) {this.i = i;this.j = j;}public final int getI() {return i;}public final void setI(int i) {this.i = i;}public final int getJ() {return j;}public final void setJ(int j) {this.j = j;}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + i;result = prime * result + j;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj == null) {return false;}if (!(obj instanceof MutableKey)) {return false;}MutableKey other = (MutableKey) obj;if (i != other.i) {return false;}if (j != other.j) {return false;}return true;}
}

测试:

public class MutableDemo {public static void main(String[] args) {// Object createdMutableKey key = new MutableKey(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了

public V get(Object key)
{   // 如果 key 是 null,调用 getForNullKey 取出对应的 value   if (key == null)   return getForNullKey();   // 根据该 key 的 hashCode 值计算它的 hash 码  int hash = hash(key.hashCode());   // 直接取出 table 数组中指定索引处的值,  for (Entry<K,V> e = table[indexFor(hash, table.length)];   e != null;   // 搜索该 Entry 链的下一个 Entr   e = e.next)         // ①
 {   Object k;   // 如果该 Entry 的 key 与被搜索 key 相同  if (e.hash == hash && ((k = e.key) == key   || key.equals(k)))   return e.value;   }   return null;
}   

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

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

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

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

import java.util.HashMap;
import java.util.Map;public class MutableDemo1 {public static void main(String[] args) {// HashMapMap<MutableKey, String> map = new HashMap<>();// Object createdMutableKey key = new MutableKey(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));}
}

输出:

Robin
null

如何解决

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

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

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

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

import java.util.HashMap;
import java.util.Map;public class MutableSafeKeyDemo {public static void main(String[] args) {Employee emp = new Employee(2);emp.setName("Robin");// Put object in HashMap.Map<Employee, String> map = new HashMap<>();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));}
}class Employee {// It is specified while object creation.// Cannot be changed once object is created. No setter for this field.private int id;private String name;public Employee(final int id) {this.id = id;}public final String getName() {return name;}public final void setName(final String name) {this.name = name;}public int getId() {return id;}// 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
    @Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + id;return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Employee other = (Employee) obj;if (id != other.id)return false;return true;}
}

输出

Showbasky
Showbasky

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

转载于:https://www.cnblogs.com/0201zcr/p/4810813.html

HashMap的key可以是可变的对象吗???相关推荐

  1. java hashmap替换key,HashMap 用可变对象作为 key 踩坑

    点击上方☝ Java编程技术乐园,轻松关注! 及时获取有趣有料的技术文章 做一个积极的人编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 作者:Icharle https://icharl ...

  2. java hashmap 初始化赋值_HashMap引用传递,对象直接赋值,修改的是同一个对象,new HashMap「」(Map)才是生成新的对象...

    HashMap引用传递:对象直接赋值,修改的是同一个对象 MapsrcMap = new HashMap<>(); srcMap.put("a","a&quo ...

  3. 蔚来一面:用Object做hashMap的Key时需要做什么?

    作者 | petterp 来源 | https://blog.csdn.net/petterp/article/details/89043847 先来说一下hashcode()和equals方法吧. ...

  4. hashmap 允许key重复吗_HashTable和HashMap的区别详解

    一.HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的, ...

  5. Java并发,volatile+不可变容器对象能保证线程安全么?!

    <Java并发编程实战>第3章原文 <Java并发编程实战>中3.4.2 示例:使用Volatile类型来发布不可变对象 在前面的UnsafeCachingFactorizer ...

  6. python可变类型与不可变类型作为函数参数区别_不要用可变类型对象做函数默认参数...

    不要用可变类型对象做函数默认参数 1. 可变对象做默认参数 内置数据类型int,float,bool,str,tuple 是不可变对象, 字典,集合,列表是可变对象. 在定义python函数时,千万不 ...

  7. 5.字符串:aa:zhangsan@163.com!bb:lisi@sina.com!cc:wangwu@126.com 将存入hashMap中 key:aa,bb,cc value:zhang

    5.字符串: "aa:zhangsan@163.com!bb:lisi@sina.com!cc:wangwu@126.com" 将存入hashMap中 key:aa,bb,cc v ...

  8. hashmap 允许key重复吗_Java HashMap key 可以重复吗?

    Java HashMap key 可以重复吗? Java的HashMap中key是不可以重复的,如果重复添加的话,HashMap会自动覆盖key一样的数据,保证一个key对应一个value,使用时只要 ...

  9. 对象作为HashMap的key

    在实际使用中如果遇到对象作为HashMap结构的key,则一定要注意重写equals和hashCode两个方法.以JDK8为例,HashMap在put(K key, V value)方法和contai ...

  10. java map 对象作为key_Java 将自定义的对象作为HashMap的key

    需要继承Map的equals函数和hashCode函数 package com.category; import java.util.HashMap; public class GenCategory ...

最新文章

  1. ER图转为关系模式(超详细,超简单)
  2. 用c语言编写5颗骰子任意投掷总数为15 的概率,概率统计习题1period;2答案
  3. 2021-01-20 Python编程特殊小技巧汇集
  4. 2.1.2 进程的状态与转换
  5. ansible高级应用示例
  6. Kubernetes的三种集群外部访问方式及使用场景说明:NodePort、LoadBalancer和Ingress
  7. python hsv inrange 范围_仅20行代码,用python给证件照换底色
  8. requests源码分析
  9. [pytorch] Pytorch入门
  10. php课程 10-35 php实现文件上传的注意事项是什么
  11. SKlearn——逻辑斯蒂回归(LR)参数设置
  12. 装机之必备软件下载合集
  13. 带时间窗的电动汽车路径规划(智能算法求解)
  14. Java图片上查找图片算法
  15. 独家策划-----让“超女”和“好男”联姻
  16. 电池充放电1c指的是什么
  17. mac使用mysql出现的错误 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
  18. linux vim粘贴快捷键设置,vim粘贴模式快捷方式
  19. 抖音CLICKID+淘宝Relaitionid回传淘积木APP内下单--转化归因联调ROI程序算法及代码技术实现
  20. 基础第三-MySQL-多表查询-索引-事务笔记

热门文章

  1. VC6和VS2005(及2010)的一些区别总结
  2. Error:Could not launch XXX failed to get the task for process 1105
  3. FPGA笔试数电部分(一)
  4. RestTemplate发起HTTPS请求
  5. mysql连接教程_MySQL 连接
  6. C# XML文件读取
  7. 栈 -- 4.1.1 Valid Parentheses I-II -- 图解
  8. 设计模式---观察者模式(C++)
  9. springmvc一个Controller类处理多个请求被遗弃的方式MultiActionController详解和新的方式
  10. V.Replication and Sharding(创建主从数据库)