2012-10-28 108 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中丢失,这实在是太离谱 - 它缓存!哇。 –