一次坑爹的填坑经历 - HashSet.remove()

经历了一次坑爹的填坑,又让我重新认识了一遍Java的collection框架。

原始场景是这样的。公司又新接回一个维护项目,不幸我被选中去接手(坑爹啊)。然后在修改一个bug中,遇到了这样一个问题。直接上代码:

Set<Map<String,Object>> dupeBySet = new HashSet<>();
...................................................
### add some constructed HashMap into dupeBySet
...................................................
for (final Bug dupeByBug : bugs) {
  if (dupeByBug.getDupeOf() == null || !dupeByBug.getDupeOf().equals(bug.getId())) {
    Iterator<Map<String, Object>> dupeBySetIter = dupeBySet.iterator();
    while (dupeBySetIter.hasNext()) {
        Map<String, Object> dupeBy = dupeBySetIter.next();
        ### start
	if (dupeByBug.getId().equals(dupeBy.get(“bugId"))) {
	    dupeBySetIter.remove();
        }
        ### end
    }
   }
}
........................................................

 

 代码主要是想从dupeBySet中删除掉一些不需要的bug id所在的map,但所碰到的问题就是当不需要的id条件满足时,它对应的map却没有从dupeBySet中删除掉。问题就出在### start到### end部分。当我做debug的时候,发现equals条件是满足的,而且remove方法也顺利的执行到了,可是很有意思的是那个对应的map却还顽强的没被删掉。

 有事求so吧,SO上一顿搜,找到了切入点:http://stackoverflow.com/questions/254441/hashset-remove-and-iterator-remove-not-working

然后再看代码,哦,原来那个dupeBySet中含有要查找的bugId的那个map的其他值在被加入后有改动。原本这个map的三个entry为“bugId"->123456, "who"-"xxx", "when"->date1, 后来根据业务需要将when改为了date2, 这直接导致了map的hashcode和equals方法变得不同,那么在调用dupeBySetIter.remove()直接导致失效。

既然找到原因,那么方案自然也就有了,直接在iterate之前先copy一下dupeBySet就ok了

Set<Map<String, Object>> copiedDupeBySet = new HashSet<>(dupeBySet);
Iterator<Map<String, Object>> dupeBySetIter = copiedDupeBySet.iterator();
while (dupeBySetIter.hasNext()) {
  Map<String, Object> dupeBy = dupeBySetIter.next();
  ### start
  if (dupeByBug.getId().equals(dupeBy.get(“bugId"))) {
    dupeBySetIter.remove();
  }
  ### end
}
dupeBySet=copiedDupeBySet

 

总结:

1. 所有基于Hash算法实现的集合类(HashSet, HashMap),在进行更改和删除操作时务必小心所要操作的元素的Hash值不被更改。

2. 使用HashMap时,如果可能,key值尽量避免使用复杂对象,这样因为误操作导致key值hash值改变的情况会大大降低。 

3. 不可变对象的重要性。(虽然在实际使用中很难保证,呵呵)

你可能感兴趣的:(hashset)