2008-10-31 42 views
28

我在HashSet上調用Iterator.remove()時遇到了問題。HashSet.remove()和Iterator.remove()不起作用

我有一組時間戳對象。在向Set中添加新項目之前,我循環遍歷該組,識別該數據對象的舊版本並將其刪除(在添加新對象之前)。時間戳包含在hashCode和equals()中,但不包含equalsData()。

for (Iterator<DataResult> i = allResults.iterator(); i.hasNext();) 
{ 
    DataResult oldData = i.next(); 
    if (data.equalsData(oldData)) 
    { 
     i.remove(); 
     break; 
    } 
} 
allResults.add(data) 

奇怪的是,i.remove()在集合中的一些項目中默默失敗(無例外)。我已驗證

  • 實際上調用了i.remove()行。我可以直接從調試器調用它在Eclipse中的斷點,它仍然無法更改設置的狀態DataResult是一個不可變的對象,因此它最初添加到集合後不能更改。

  • equals和hashCode()方法使用@Override來確保它們是正確的方法。單元測試驗證了這些工作。

  • 這也失敗,如果我只是使用for語句和Set.remove來代替。 (例如遍歷項目,找到列表中的項目,然後在循環後調用Set.remove(oldData))。

  • 我在JDK 5和JDK 6

測試,我想我必須失去了一些基本的東西,但花一些時間顯著在這我的同事後,我被難住了。任何建議要檢查的東西?

編輯:

已經有問題 - 是DataResult真正一成不變的。是。沒有二傳手。當Date對象被檢索(這是一個可變對象)時,它通過創建一個副本來完成。

public Date getEntryTime() 
{ 
    return DateUtil.copyDate(entryTime); 
} 

public static Date copyDate(Date date) 
{ 
    return (date == null) ? null : new Date(date.getTime()); 
} 

進一步編輯(一段時間後): 爲了記錄 - DataResult是不是一成不變的!它引用了一個哈希代碼在持久化到數據庫時發生更改的對象(我知道不好的做法)。事實證明,如果DataResult是使用瞬態子對象創建的,並且子對象被持久保存,則DataResult哈希碼已更改。

非常微妙 - 我看了很多次,並沒有注意到缺乏不變性。

+0

兩種可能性。你說DataResult是不可變的。假設值由構造函數設置並且沒有設置方法是否安全? 2.你的equals和hashcode不能按照你的預期工作。你可以發佈這兩個代碼嗎? – 2008-10-31 21:31:50

回答

35

我對此一個非常奇怪的是,並寫了如下測試:

import java.util.HashSet; 
import java.util.Iterator; 
import java.util.Random; 
import java.util.Set; 

public class HashCodeTest { 
    private int hashCode = 0; 

    @Override public int hashCode() { 
     return hashCode ++; 
    } 

    public static void main(String[] args) { 
     Set<HashCodeTest> set = new HashSet<HashCodeTest>(); 

     set.add(new HashCodeTest()); 
     System.out.println(set.size()); 
     for (Iterator<HashCodeTest> iter = set.iterator(); 
       iter.hasNext();) { 
      iter.next(); 
      iter.remove(); 
     } 
     System.out.println(set.size()); 
    } 
} 

導致:

1 
1 

如果因爲它的對象的hashCode()值已更改被添加到HashSet中,它似乎使對象不可移動。

我不確定這是否是您遇到的問題,但如果您決定重新訪問此問題,則需要查看一下。

1

您是否嘗試過類似

boolean removed = allResults.remove(oldData) 
if (!removed) // COMPLAIN BITTERLY! 

換句話說,從集中刪除對象並打破循環。這不會導致Iterator抱怨。我不認爲這是一個長期的解決方案,但可能會爲您提供有關hashCodeequalsequalsData方法

-2

如果有兩個條目具有相同的數據,其中只有一個是更換了一些信息......有你佔了那個?以防萬一,你是否嘗試過另一個不使用散列碼的集合數據結構,比如List?

+2

一套不允許愚弄... – 2008-11-01 21:50:05

+0

這是正確的,但愚蠢由equals()方法定義,在這種情況下,使用數據+時間戳。 – 2008-11-02 04:24:41

2

你確定DataResult是不可變的嗎?什麼是時間戳的類型?如果是java.util.Date,那麼當您初始化DataResult時,您是否正在複製它?請記住java.util.Date是可變的。

例如:

Date timestamp = new Date(); 
DataResult d = new DataResult(timestamp); 
System.out.println(d.getTimestamp()); 
timestamp.setTime(System.currentTimeMillis()); 
System.out.println(d.getTimestamp()); 

將打印兩個不同的時間。

如果你可以發佈一些源代碼,它也會有所幫助。

+0

@Jack Leow:這是一個很棒的評論。不過,我在通過getter暴露之前複製日期對象。 (最近閱讀了Bloch的Effectiva Java第2版)。所以DataResult是真正不可變的。 – 2008-11-01 21:06:03

+0

複製值或參考? – 2008-11-01 21:51:04

6

HashSet使用HashMap,當HashSet.remove(Object)或Iterator.remove()被調用時調用HashMap.removeEntryForKey(Object)。此方法同時使用hashCode()和equals()來驗證它正在從集合中刪除適當的對象。

如果Iterator.remove()和HashSet.remove(Object)都不起作用,那麼equals()或hashCode()方法肯定有問題。發佈這些代碼將有助於診斷您的問題。

-4

我對Java的速度並不熟悉,但我知道,當您在.NET中迭代該集合時,您無法從集合中刪除項目,但.NET會在捕獲時拋出異常這個。這可能是問題嗎?

2

感謝您的幫助。我懷疑這個問題必須和spencerk建議的equals()和hashCode()一樣。我沒有在我的調試器和單元測試中檢查這些,但我必須缺少一些東西。

我最終做了一個解決方法 - 將除了一個以外的所有項目複製到一個新的Set。對於踢腿,我使用了Apache Commons CollectionUtils。

Set<DataResult> tempResults = new HashSet<DataResult>(); 
    CollectionUtils.select(allResults, 
      new Predicate() 
      { 
       public boolean evaluate(Object oldData) 
       { 
        return !data.equalsData((DataResult) oldData); 
       } 
      } 
      , tempResults); 
    allResults = tempResults; 

我打算在這裏停止 - 太多工作來簡化爲一個簡單的測試用例。但幫助是miuch讚賞。

1

幾乎可以肯定的是,這些哈希碼不匹配「equals()」的舊數據和新數據。我之前遇到過這種事情,而且實際上最終會爲每個對象和字符串表示形式散列hashcodes,並試圖找出錯配發生的原因。

如果您比較項目前/後數據庫,有時會丟失納秒(取決於您的數據庫列類型),這可能會導致哈希碼發生更改。

2

如果其子類型的哈希碼取決於其可變狀態,那麼您應該都小心任何通過哈希碼獲取其子級的Java集合。舉個例子:

HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap variant: 

的HashSet通過其檢索的hashCode的項目,但其項目類型 是一個HashSet,並hashSet.hashCode取決於其項目的狀態。

代碼爲此事:

HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>(); 
HashSet<String> set1 = new HashSet<String>(); 
set1.add("1"); 
coll.add(set1); 
print(set1.hashCode()); //---> will output X 
set1.add("2"); 
print(set1.hashCode()); //---> will output Y 
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY) 

原因是是的HashSet的刪除方法使用HashMap和它的標識的hashCode鍵,而AbstractSet的的hashCode是動態的,取決於自身的可變特性。