2012-10-28 62 views
8

假設您有一個類並且創建了一個可以存儲此類的這個實例的HashSet。如果您嘗試添加相同的實例,則只有一個實例保留在集合中,這很好。如果包含的元素被修改,則Java HashSet包含重複項

但是,如果你有在HashSet的兩個不同的實例,和你拍一,並使其成爲其他的精確副本(通過複製字段),HashSet中隨後將包含兩個重複的實例。

這裏是演示了此代碼:

public static void main(String[] args) 
    { 
     HashSet<GraphEdge> set = new HashSet<>(); 
     GraphEdge edge1 = new GraphEdge(1, "a"); 
     GraphEdge edge2 = new GraphEdge(2, "b"); 
     GraphEdge edge3 = new GraphEdge(3, "c"); 

     set.add(edge1); 
     set.add(edge2); 
     set.add(edge3); 

     edge2.setId(1); 
     edge2.setName("a"); 

     for(GraphEdge edge: set) 
     { 
      System.out.println(edge.toString()); 
     } 

     if(edge2.equals(edge1)) 
     { 
      System.out.println("Equals"); 
     } 
     else 
     { 
      System.out.println("Not Equals"); 
     } 
    } 

    public class GraphEdge 
    { 
     private int id; 
     private String name; 

     //Constructor ... 

     //Getters & Setters... 

     public int hashCode() 
     { 
     int hash = 7; 
     hash = 47 * hash + this.id; 
     hash = 47 * hash + Objects.hashCode(this.name); 
     return hash;  
     } 

     public boolean equals(Object o) 
     { 
      if(o == this) 
      { 
       return true; 
      } 

      if(o instanceof GraphEdge) 
      { 
       GraphEdge anotherGraphEdge = (GraphEdge) o; 
       if(anotherGraphEdge.getId() == this.id && anotherGraphEdge.getName().equals(this.name)) 
       { 
        return true; 
       } 
      } 

       return false; 
     } 
    } 

從上面的代碼的輸出:

1 a 
1 a 
3 c 
Equals 

有沒有辦法迫使HashSet的,以驗證其內容,這樣可以重複條目像上面的場景中創建一樣被刪除?

一個可能的解決方案是創建一個新的HashSet和內容從一個HashSet的複製到另一個,使新的HashSet將不包含重複的,但是我不喜歡這樣的解決方案。

回答

16

您所描述的情況是無效的。請參閱Javadoc:「如果在對象是集合中的元素時,以影響等於比較的方式更改對象的值,則不會指定集合的​​行爲。」

+0

好吧,所以上述情況是無效的。我想唯一的選擇是將內容複製到一個新的HashSet。 –

+4

@ Spi1988正確的解決方案是堅持'Set'的契約,並且在將對象添加到集合後不要修改對象。 – EJP

+0

@PB_MLT通過將內容複製到新的HashSet中可以實現什麼功能? – HungryForKnowledge

-1

Objects.hashCode旨在被用來生成用參數對象hascode。您正在使用它作爲hascode計算的一部分。

嘗試用以下替換您的hashCode實現:

public int hashCode() 
{ 
    return Objects.hashCode(this.id, this.name); 
} 
+0

Objects.hashCode(this.id,this.name)無效,因爲hashCode方法只接受一個對象。 –

+0

我假定您使用的是Google Collections圖書館: –

+0

http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Objects.html#hashCode(java.lang.Object。 ..) –

1

你是正確的,我不認爲有什麼辦法來防止你討論的情況。所有使用散列和等號的集合都會受到這個問題的困擾。該集合沒有通知,該對象自從添加到集合後已經發生更改。我認爲你提出的解決方案很好。

如果你這麼關注這個問題,也許你需要重新考慮你的數據結構。您可以使用不可變對象作爲例子。對於不可變的對象,你不會有這個問題。

1

HashSet不知道其成員的屬性對象已被添加之後改變。如果這對您是個問題,那麼您可能需要考慮使GraphEdge不可變。例如:

GraphEdge edge4 = edge2.changeName("new_name"); 

GraphEdge是不可變的,在返回一個新實例,而改變現有實例改變值結果的情況下。

-1

您需要在迭代列表時執行唯一檢測。製作一個新的HashSet似乎不是正確的路要走,但爲什麼不試試這個......也許不是用一個HashSet下手......

public class TestIterator { 
    public static void main(String[] args) { 
     List<String> list = new ArrayList<String>(); 

     list.add("1"); 
     list.add("1"); 
     list.add("2"); 
     list.add("3"); 

     for (String s : new UniqueIterator<String>(list)) { 
      System.out.println(s); 
     } 
    } 
} 

public class UniqueIterator<T> implements Iterable<T> { 
    private Set<T> hashSet = new HashSet<T>(); 

    public UniqueIterator(Iterable<T> iterable) { 
     for (T t : iterable) { 
      hashSet.add(t); 
     } 
    } 

    public Iterator<T> iterator() { 
     return hashSet.iterator(); 
    } 
} 
+0

他沒有列表。他有一套。他濫用了它。沒有答案。 – EJP

+0

他正在使用一組作爲列表。所以他需要正確使用這個設置或者使用一個列表。 – slipperyseal

+0

他不想要一個列表。他想要一套。他有一套。他濫用它,然後想知道爲什麼它的元素不是唯一的。解決方案並不是讓事情變得更糟,而是首先阻止它發生。 – EJP

3

要添加到@ EJP的回答,會發生什麼在實踐中,如果將HashSet中的對象變異以使它們重複(在equals/hashcode合同中),則哈希表數據結構將中斷。

  • 取決於突變的具體細節,以及哈希表,一個或兩個實例的狀態將變爲不可見查找(例如contains等操作)。要麼它位於錯誤的哈希鏈上,要麼因爲其他實例出現在哈希鏈之前。而且很難預測哪個實例可見......以及它是否仍然可見。

  • 如果迭代集合,兩個實例仍然存在......違反Set合同。

當然,這是從應用程序的角度來看非常破碎。


您可以避免這個問題有兩種方法:使用不可變類型的元素集合

  • 使得對象的副本,當你把它們放到一組和/或拉他們出了一組,
  • 編寫代碼,以便它「知道」不改變對象的時間...

從正確性和魯棒性的角度來看,第一種選擇顯然是最好的。


順便說一句,要用一般的方法「解決」這個確實很困難。 Java中沒有普遍的機制來知道......或被通知......某些元素已經改變。你可以在類的基礎上實現這樣的機制,但必須明確地編碼(並且它不會便宜)。即使你有這樣的機制,你會怎麼做?顯然,其中一個對象現在應該從集合中刪除......但是哪一個呢?

+0

Thx爲解釋。如果您有一種機制可以檢測到集合中的某個對象發生了變化,並且現在與另一個存在於同一集合中的對象相同,那麼您可以刪除任何一個重複對象(從哪個對象中刪除並不重要他們是平等的)。 –

+0

@ Spi1988 - *「不管你刪除哪一個,因爲它們是平等的」*。一般情況並非如此。 「equals()」返回「true」的兩個對象不必相同。而且你可以放棄哪一個。你所假設的機制是假設的。 –

+0

謝謝,我現在正在爲此掙扎數小時。但老實說,這個問題只發生在實現懶得做一個合適的HashSet而不是HashTable的備份,從而將hashCode索引凍結到創建時間。據我所知,這個HashSet他們給我們不是一個HashSet,但ImmutableHashSet和一個合適的HashSet實現仍然從jdk中丟失,這實在是太離譜 - 它緩存!哇。 –