刚学 Java 集合的人,十有八九都栽过 “看着简单用着错” 的跟头 —— 要么 ArrayList 增删卡得不行,要么 HashSet 莫名其妙存进重复数据,甚至 HashMap 遍历删元素直接抛异常。这些问题真不是你记性差,而是没吃透集合类的底层逻辑,小索奇认为,集合类的核心坑点都藏在 “选择” 和 “使用细节” 里,今天就把最实用的避坑干货聊透。
你是不是刚开始学的时候,不管什么场景都顺手 new 个 ArrayList?反正课本里常提,API 也好用。但要是碰到需要频繁在中间插数据、删数据的场景,这操作简直是 “自找罪受”。
这背后的原因特简单:ArrayList 底层是动态数组,就像一排连续的抽屉,中间插东西得把后面的抽屉全往后挪;而 LinkedList 是双向链表,相当于一串珠子,插东西只要把前后珠子的连接改一下就行。小索奇见过不少新手写消息队列模拟代码,用 ArrayList 循环插入消息,数据量过万后卡得半天出结果,换成 LinkedList 后速度直接快了十倍不止。所以记住:查得多、改得少用 ArrayList,增删频繁(尤其是中间位置)就用 LinkedList,别再 “一把 ArrayList 走天下” 了。
更让人头大的是 HashSet 存重复数据的问题。明明说好 “不存重复元素”,可自己定义的对象扔进去,偏偏能存好几个一样的,这到底是为啥?
其实 HashSet 的 “去重” 是有条件的,它得先通过 hashCode () 判断对象的哈希值,再用 equals () 确认是否真的相同。要是你自定义的类(比如 User、Product)没重写这两个方法,Java 就会用默认的 ——hashCode () 返回对象的内存地址,equals () 也是比较内存地址。这就导致哪怕两个对象的属性完全一样,只要是不同的实例,HashSet 就认为是 “不同的”,自然会存进去。
正确的做法是:自定义对象要存 HashSet,必须重写 hashCode () 和 equals ()。比如 User 类按 name 和 id 去重,就在 equals () 里判断这两个属性是否相等,hashCode () 也基于这两个属性计算(可以用 Objects.hash (name, id))。这样一来,属性相同的对象就会被 HashSet 识别为重复,再也不会出现 “伪重复” 问题了。
还有个高频坑:HashMap 遍历的时候删元素。不少人习惯用 for-each 循环遍历,顺手就调用 map.remove (key),结果直接抛出 ConcurrentModificationException(并发修改异常),当场懵圈。
这不是 HashMap “脾气怪”,而是它的 “快速失败” 机制在起作用。for-each 遍历本质是用迭代器,迭代器会记录一个 modCount(修改次数),每次遍历前都检查这个值有没有变,一旦发现你在遍历中改了集合(比如 remove),modCount 变了,就直接抛异常。
那想边遍历边删怎么办?有两个靠谱办法:要么用迭代器的 remove () 方法,比如 Iterator<Map.Entry<String, Integer>> it = map.entrySet ().iterator (); 遍历的时候用 it.remove ();要么用 Java 8 的 removeIf () 方法,一行代码就能搞定。千万别再用 for-each 直接删了,这坑踩一次就够记一辈子。
对了,最后提个容易被忽略的点:集合初始化别忘指定容量。比如 new ArrayList () 默认容量是 10,数据满了就会扩容(每次扩大 1.5 倍),扩容得复制旧数组到新数组,次数多了特耗资源。要是你大概知道会存多少数据,比如 200 个,直接 new ArrayList (200),能省不少扩容的开销。小索奇见过有人存 1000 条数据,ArrayList 扩容了七八次,这点细节虽小,但对性能影响真不小。
Java 集合看着 API 简单,实则每个类都有自己的 “脾气”,踩坑不可怕,关键是搞懂底层逻辑。你学集合时栽过哪些跟头?是选错集合类型还是用错方法?评论区聊聊,让更多新手少走弯路。
我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源