2012-10-28 10 views
8

Nehmen wir an, Sie haben eine Klasse und Sie erstellen ein HashSet, das diese Instanzen dieser Klasse speichern kann. Wenn Sie versuchen, Instanzen hinzuzufügen, die gleich sind, wird nur eine Instanz in der Auflistung beibehalten, und das ist in Ordnung.Java HashSet enthält Duplikate, wenn das enthaltene Element geändert wurde

Wenn Sie jedoch zwei verschiedene Instanzen im HashSet haben und eine davon nehmen und eine exakte Kopie der anderen erstellen (indem Sie die Felder kopieren), enthält das HashSet zwei doppelte Instanzen.

Hier ist der Code, der dies zeigt:

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; 
     } 
    } 

Die Ausgabe aus dem obigen Code:

1 a 
1 a 
3 c 
Equals 

Gibt es eine Möglichkeit zu zwingen, die HashSet zu validieren seinen Inhalt, so dass mögliche doppelte Einträge erstellt wie im obigen Szenario erhalten entfernt?

Eine mögliche Lösung könnte sein, ein neues HashSet zu erstellen und den Inhalt von einem Hashset auf ein anderes zu kopieren, so dass das neue Hashset keine Duplikate enthält, aber ich mag diese Lösung nicht.

Antwort

16

Die Situation, die Sie beschreiben, ist ungültig. Siehe Javadoc: "Das Verhalten einer Menge wird nicht angegeben, wenn der Wert eines Objekts in einer Weise geändert wird, die Gleichheitsvergleiche beeinflusst, während das Objekt ein Element in der Menge ist."

+0

Okay, so das obige Szenario ist ungültig. Ich denke, die einzige Möglichkeit besteht darin, den Inhalt in ein neues HashSet zu kopieren. –

+4

@ Spi1988 Die richtige Lösung besteht darin, sich an den Vertrag von 'Set' zu halten und Objekte nicht zu ändern, nachdem Sie sie der Sammlung hinzugefügt haben. – EJP

+0

@PB_MLT was erreichen Sie, indem Sie den Inhalt in neues HashSet kopieren? – HungryForKnowledge

-1

Objects.shashCode wird verwendet, um mithilfe von Parameterobjekten einen Haskode zu generieren. Sie verwenden es als Teil der Hascode-Berechnung.

Versuchen Sie, Ihre Implementierung von hashCode mit folgenden ersetzen:

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

Objects.shashCode (this.id, this.name) ist nicht gültig, da die Methode hashCode nur ein Objekt benötigt. –

+0

Ich nehme an, dass Sie die Google Collections-Bibliothek verwendet haben: –

+0

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

1

Sie haben Recht, und ich glaube nicht, dass es einen Schutz gegen den Fall gibt, den Sie diskutieren. Alle Sammlungen, die Hashing und Equals verwenden, unterliegen diesem Problem. Die Sammlung enthält keine Benachrichtigung darüber, dass sich das Objekt seit dem Hinzufügen zur Sammlung geändert hat. Ich denke, die Lösung, die Sie skizzieren, ist gut.

Wenn Sie so besorgt über dieses Problem sind, müssen Sie vielleicht Ihre Datenstrukturen überdenken. Sie könnten zum Beispiel unveränderliche Objekte verwenden. Mit unveränderlichen Objekten hätten Sie dieses Problem nicht.

1

HashSet ist nicht bekannt, dass sich die Eigenschaften seines Elements ändern, nachdem das Objekt hinzugefügt wurde. Wenn dies ein Problem für Sie ist, sollten Sie in Betracht ziehen, GraphEdge unveränderlich zu machen. Zum Beispiel:

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

In dem Fall, wo GraphEdge unveränderlich ist, eine neue Instanz bei der Rückkehr eher Wert Ergebnis ändert die vorhandene Instanz zu ändern.

-1

Sie müssen die einmalige Erkennung durchführen, wenn Sie Ihre Liste iterieren. eine neue HashSet macht vielleicht nicht den richtigen Weg scheint zu gehen, aber warum nicht ... versuchen, ein HashSet mit zu beginnen verwendet vielleicht nicht ...

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

Er hat keine Liste. Er hat ein Set. Er missbraucht es. Keine Antwort. – EJP

+0

Er verwendet ein Set als Liste. Also muss er das Set richtig benutzen ODER eine Liste verwenden. – slipperyseal

+0

Er will keine Liste. Er will ein Set. Er hat ein Set. Er missbraucht es und fragt sich dann, warum seine Elemente nicht einzigartig sind. Die Lösung besteht nicht darin, es zu verschlimmern, sondern zu verhindern, dass es überhaupt passiert. – EJP

3

zu @ EJP Antwort hinzuzufügen, was passieren wird, in der Praxis, wenn Sie Objekte in einem HashSet zu Duplikate mutieren (im Sinne des equals/hashcode Vertrag) ist, dass die Hash-Tabelle Datenstruktur wird brechen.

  • Abhängig von den genauen Details der Mutation und der Zustand der Hash-Tabelle, eines oder beide der Instanzen wird unsichtbar Nachschlag (z contains und andere Operationen). Entweder befindet es sich in der falschen Hash-Kette oder weil die andere Instanz davor in der Hash-Kette erscheint. Und es ist schwer vorherzusagen, welche Instanz sichtbar sein wird ... und ob sie sichtbar bleibt.

  • Wenn Sie den Satz iterieren, sind beide Instanzen immer noch vorhanden ... in Verletzung des Set Vertrags.

Natürlich ist dies sehr aus der Perspektive der Anwendung gebrochen.


Sie vermeiden dieses Problem, indem entweder:

  • einen unveränderlichen Typen für Ihre Set-Elemente verwenden,
  • eine Kopie der Objekte zu machen, wie Sie sie in den Satz setzen und/oder ziehen sie aus dem Satz,
  • Schreiben sie den Code, so dass es „weiß“ nicht, die Objekte für die Dauer zu ändern ...

Aus Sicht der Korrektheit und Robustheit ist die erste Option eindeutig die beste.


Übrigens wäre es wirklich schwierig, dies in einer allgemeinen Weise zu "reparieren". Es gibt keinen durchdringenden Mechanismus in Java, um zu wissen ... oder benachrichtigt zu werden ... dass sich ein Element geändert hat. Sie können einen solchen Mechanismus auf Klassenbasis implementieren, aber er muss explizit codiert werden (und er wird nicht billig sein). Selbst wenn Sie einen solchen Mechanismus hätten, was würden Sie tun? Offensichtlich sollte nun eines der Objekte aus dem Set entfernt werden ... aber welches?

+0

Thx für die Erklärung.Wenn Sie einen Mechanismus hätten, der erkennen könnte, dass sich ein Objekt in einem Set geändert hat und nun einem anderen Objekt in demselben Set entspricht, dann können Sie einfach eines der Duplikate entfernen (es spielt keine Rolle, welches Sie seitdem entfernen) Sie sind gleich). –

+0

@ Spi1988 - * "es spielt keine Rolle, welche Sie entfernen, da sie gleich sind" *. Das ist im Allgemeinen nicht wahr. Zwei Objekte, für die 'equals()' 'true' zurückgibt, müssen nicht identisch sein. Und es könnte wichtig sein, welchen du fallen lässt. Und der Mechanismus, den Sie postulieren, ist hypothetisch. –

+0

Danke, ich habe jetzt stundenlang damit zu kämpfen. Aber ehrlich gesagt, dieses ganze Problem tritt nur auf, weil die Implementierung zu faul war, um ein korrektes HashSet zu erstellen, anstatt es nur durch eine HashTable zu sichern, wodurch die HashCode-Indizierung zur Erstellungszeit eingefroren wurde. Soweit mir bekannt ist, ist dieses HashSet, das sie uns geben, kein HashSet, aber ein ImmutableHashSet und eine richtige HashSet-Implementierung fehlen noch im jdk, das ist wirklich unverschämt - es cachiert !!!! Beeindruckend. –