作者 | 楼下小黑哥

头图 | CSDN下载自图虫

作为 List 集合好兄弟 Map,我们天天都在使用,一不小心就会踩坑。

今天我就来总结这些常见的坑,再捞自己一手,防止后续同学再继续踩坑。

本文设计知识点如下:

不是所有的 Map 都能包含 null

这个踩坑经历还是发生在实习的时候,那时候有这样一段业务代码,功能很简单,从 XML 中读取相关配置,存入 Map 中。

代码示例如下:

那时候正好有个小需求,需要改动一下这段业务代码。改动的过程中,突然想到 HashMap 并发过程可能导致死锁的问题。

于是改动了一下这段代码,将 HashMap 修改成了 ConcurrentHashMap。

美滋滋提交了代码,然后当天上线的时候,就发现炸了。。。

应用启动过程发生 NPE 问题,导致应用启动失败。

根据异常日志,很快就定位到了问题原因。由于 XML 某一项配置问题,导致读取元素为 null,然后元素置入到 ConcurrentHashMap 中,抛出了空指针异常。

这不科学啊!之前 HashMap 都没问题,都可以存在 null,为什么它老弟 ConcurrentHashMap 就不可以?

翻阅了一下 ConcurrentHashMap#put 方法的源码,开头就看到了对 KV 的判空校验。

看到这里,不知道你有没有疑惑,为什么 ConcurrentHashMap 与 HashMap 设计的判断逻辑不一样?

求助了下万能的 Google,找到 Doug Lea 老爷子的回答:

总结一下:

  • null 会引起歧义,如果 value 为 null,我们无法得知是值为 null,还是 key 未映射具体值?

  • Doug Lea 并不喜欢 null,认为 null 就是个隐藏的炸弹。

上面提到 Josh Bloch 正是 HashMap 作者,他与 Doug Lea 在 null 问题意见并不一致。

也许正是因为这些原因,从而导致 ConcurrentHashMap 与 HashMap 对于 null 处理并不一样。

最后贴一下常用 Map 子类集合对于 null 存储情况:

上面的实现类约束,都太不一样,有点不好记忆。其实只要我们在加入元素之前,主动去做空指针判断,不要在 Map 中存入 null,就可以从容避免上面问题。

自定义对象为 key

先来看个简单的例子,我们自定义一个 Goods 商品类,将其作为 Key 存在 Map 中。

示例代码如下:

上面代码中,第二次我们加入一个相同的商品,原本我们期望新加入的值将会替换原来旧值。但是实际上这里并没有替换成功,反而又加入一对键值。

翻看一下 HashMap#put 的源码:

以下代码基于 JDK1.7

这里首先判断 hashCode 计算产生的 hash,如果相等,再判断 equals 的结果。但是由于 Goods对象未重写的hashCode 与 equals 方法,默认情况下 hashCode 将会使用父类对象 Object 方法逻辑。

而 Object#hashCode 是一个 native 方法,默认将会为每一个对象生成不同 hashcode(与内存地址有关),这就导致上面的情况。

所以如果需要使用自定义对象做为 Map 集合的 key,那么一定记得重写hashCode 与 equals 方法。

然后当你为自定义对象重写上面两个方法,接下去又可能踩坑另外一个坑。

使用 lombok 的 EqualsAndHashCode 自动重写 hashCode 与 equals 方法。

上面的代码中,当 Map 中置入自定义对象后,接着修改了商品金额。然后当我们想根据同一个对象取出 Map 中存的值时,却发现取不出来了。

上面的问题主要是因为 get 方法是根据对象 的 hashcode 计算产生的 hash 值取定位内部存储位置。

当我们修改了金额字段后,导致 Goods 对象 hashcode 产生的了变化,从而导致 get 方法无法获取到值。

通过上面两种情况,可以看到使用自定义对象作为 Map 集合 key,还是挺容易踩坑的。

所以尽量避免使用自定义对象作为 Map 集合 key,如果一定要使用,记得重写 hashCode 与 equals 方法。另外还要保证这是一个不可变对象,即对象创建之后,无法再修改里面字段值。

错用 ConcurrentHashMap 导致线程不安全

之前的文章『每天都在用 Map,这些核心技术你知道吗?』我们说过 HashMap 是一个线程不安全的容器,多线程环境为了线程安全,我们需要使用 ConcurrentHashMap代替。

但是不要认为使用了 ConcurrentHashMap 一定就能保证线程安全,在某些错误的使用场景下,依然会造成线程不安全。

上面示例代码,我们原本期望输出 1001,但是运行几次,得到结果都是小于 1001。

深入分析这个问题原因,实际上是因为第一步与第二步是一个组合逻辑,不是一个原子操作。

ConcurrentHashMap 只能保证这两步单的操作是个原子操作,线程安全。但是并不能保证两个组合逻辑线程安全,很有可能 A 线程刚通过 get 方法取到值,还未来得及加 1,线程发生了切换,B 线程也进来取到同样的值。

这个问题同样也发生在其他线程安全的容器,比如 Vector等。

上面的问题解决办法也很简单,加锁就可以解决,不过这样就会使性能大打折扣,所以不太推荐。

我们可以使用 AtomicInteger 解决以上的问题。

List 集合这些坑,Map 中也有

上一篇文章中我们提过,Arrays#asList 与 List#subList 返回 List 将会与原集合互相影响,且可能并不支持 add 等方法。同样的,这些坑爹的特性在 Map 中也存在,一不小心,将会再次掉坑。

Map 接口除了支持增删改查功能以外,还有三个特有的方法,能返回所有 key,返回所有的 value,返回所有 kv 键值对。

// 返回 key 的
set 视图Set<K> keySet();
// 返回所有 value
Collection 视图Collection<V> values();
// 返回 key-value 的
set 视图Set<Map.Entry<K, V>> entrySet();

这三个方法创建返回新集合,底层其实都依赖的原有 Map 中数据,所以一旦 Map 中元素变动,就会同步影响返回的集合。

另外这三个方法返回新集合,是不支持的新增以及修改操作的,但是却支持 clear、remove 等操作。

示例代码如下:

所以如果需要对外返回 Map 这三个方法产生的集合,建议再来个套娃。

new ArrayList<>(map.values());

最后再简单提一下,使用 foreach 方式遍历新增/删除 Map 中元素,也将会和 List 集合一样,抛出 ConcurrentModificationException。

总结

从上面文章可以看到不管是 List 提供的方法返回集合,还是 Map 中方法返回集合,底层实际还是使用原有集合的元素,这就导致两者将会被互相影响。所以如果需要对外返回,请使用套娃大法,这样让别人用的也安心。

第二, Map 各个实现类对于 null 的约束都不太一样,这里建议在 Map 中加入元素之前,主动进行空指针判断,提前发现问题。

第三,慎用自定义对象作为 Map 中的 key,如果需要使用,一定要重写 hashCode 与 equals 方法,并且还要保证这是个不可变对象。

第三,ConcurrentHashMap 是线程安全的容器,但是不要思维定势,不要片面认为使用 ConcurrentHashMap 就会线程安全。

版权声明:本文为CSDN博主「楼下小黑哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/u014634309/java/article/details/105964392

【END】

更多精彩推荐
☞汉芯一号、木兰语言再到天赐 OS,国产基础软件十年泣血,梦想何圆?
☞JavaScript 虽火,但不要轻易去追随!
☞密码界“女杀手”,破译世上最安全密码系统,获 771 万奖金!
☞138 张图带你 MySQL 入门!
☞独家揭秘!抖音爆款漫画变身特效的背后技术
☞2013年买了100万美元比特币却希望“比特币归零”,这位亿万富翁公开“比特币鲸鱼”身份
你点的每个“在看”,我都认真当成了喜欢

编程坑太多,Map 集合怎么也有这么多坑?一不小心又踩了好几个!| 原力计划...相关推荐

  1. Map 集合的坑你踩过几个?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「程序通事」 上一篇 List 踩坑文章 ...

  2. Go 语言编程 — 高级数据类型 — Map 集合

    目录 文章目录 目录 集合 定义 Map delete() 函数 实现简单 HashMap 集合 集合(Map)是一种无序的 HASH 键值对集合.通过 key 来快速检索 value,所以我们可以像 ...

  3. Java编程基础19——Map集合斗地主案例

    1_Map集合概述和特点 A:Map接口概述 查看API可以知道: 将键映射到值的对象 一个映射不能包含重复的键 每个键最多只能映射到一个值 B:Map接口和Collection接口的不同 Map是双 ...

  4. 《深入理解ES6》笔记——Set集合与Map集合(7)

    Map和Set都叫做集合,但是他们也有所不同.Set常被用来检查对象中是否存在某个键名,Map集合常被用来获取已存的信息. Set Set是有序列表,含有相互独立的非重复值. 创建Set 既然我们现在 ...

  5. Java-Collection集合和Map集合总结

    本文欢迎转载,转载前请联系作者,经允许后方可转载.转载后请注明出处,谢谢! http://blog.csdn.net/colton_null 作者:喝酒不骑马 Colton_Null from CSD ...

  6. map集合---------今日份下饭菜,妈妈再也不用担心我饿肚子了,学好List, Set,与Map,装的下,世界就是你的

    "靡不有初,鲜克有终",读音是"mǐ bù yǒu chū,xiǎn kè yǒu zhōng",原意是凡事都有个开始,但经常不了了之,没个结果.后借此语以讽谕 ...

  7. Map集合、Stream流、File类、递归

    一,JDK8新特性:Stream 1,认识Stream 也叫Stream流,是jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据. 优势: Stre ...

  8. java 中map_Java Map集合详解

    Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键对象和一个值对象.其中,键对象不允许重复,而值对象可以重复,并且值对象还可以是 Map 类型的,就像数组中的元素 ...

  9. Java容器 | 基于源码分析Map集合体系

    一.容器之Map集合 集合体系的源码中,Map中的HashMap的设计堪称最经典,涉及数据结构.编程思想.哈希计算等等,在日常开发中对于一些源码的思想进行参考借鉴还是很有必要的. 基础:元素增查删.容 ...

最新文章

  1. Python使用matplotlib可视化发散型点图、发散型点图可以同时处理负值和正值、并按照大小排序区分数据、为发散型点图添加数值标签(Diverging Dot Plot )
  2. Jupyter notebook入门教程(下)
  3. 从零开始配置服务器密码机的开发环境
  4. 十五、详述 IntelliJ IDEA 插件的安装及使用方法
  5. gcc编译器java_「gcc编译器下载」gcc编译器下载各版本下载 - seo实验室
  6. 理解C#中的里氏转换
  7. springboot链接数据库的bug
  8. winform公共标签和常用属性
  9. Matlab读取音频文件并进行分析
  10. ubuntu装指定分区_安装Ubuntu时如何手动指定分区(高级分区)
  11. Python 生命游戏(tkinter版)
  12. 基于Java的四种算法设计(1.螺钉和螺母问题 2.九宫格问题 3.最大总和问题 4.地图着色问题)
  13. 车型代号对照表_上海大众车型与VIN代号对照表
  14. simulink电子节气门控制模型发动机电子节气门控制模型,有说明文档,教程。
  15. 外贸独立站平台十大排名-找到最合适的外贸平台
  16. UltraEdit+Masm--打造自己的汇编IDE
  17. 台式机安装EXSI,通过官方方式定制安装包
  18. LaTeX Warnings: Unused global option(s)
  19. java 相关论坛或网站
  20. Object.assign 原理及其实现

热门文章

  1. Git清除用户名和密码
  2. js实现点击“验证码”开始倒计时
  3. Tensorflow训练神经网络
  4. Site error: the ionCube PHP Loader needs to be installed.解决办法
  5. java 中的 ThreadLocal
  6. 项目功能大全,让你的项目一天搞定
  7. Jquery重新学习之七[Ajax运用总结A]
  8. 《TensorFlow 2.0深度学习算法实战教材》学习笔记(八、过拟合)
  9. [贪心|双指针] leetcode 11 盛最多水的容器
  10. 【前端】【cornerstionjs】Cornerstone加载base64表示的jpg图像