Java HashSet contiene duplicados si el elemento contenido se modifica

Digamos que tienes una clase y creas un HashSet que puede almacenar estas instancias de esta clase. Si intenta agregar instancias que son iguales, solo se guarda una instancia en la colección, y eso está bien.

Sin embargo, si tiene dos instancias diferentes en el HashSet, y toma una y la convierte en una copia exacta de la otra (copiando los campos), el HashSet contendrá dos instancias duplicadas.

Aquí está el código que demuestra esto:

public static void main(String[] args) { HashSet 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; } } 

El resultado del código anterior:

 1 a 1 a 3 c Equals 

¿Hay alguna manera de forzar al HashSet a validar sus contenidos para que se eliminen las posibles entradas duplicadas creadas como en el escenario anterior?

Una posible solución podría ser crear un nuevo HashSet y copiar el contenido de un hashset a otro para que el nuevo hashset no contenga duplicados, pero no me gusta esta solución.

La situación que describes no es válida. Ver el Javadoc : “El comportamiento de un conjunto no se especifica si el valor de un objeto se cambia de una manera que afecta a las comparaciones iguales, mientras que el objeto es un elemento en el conjunto”.

Para agregar a la respuesta de @ EJP, lo que sucederá en la práctica si muta objetos en un HashSet para hacerlos duplicados (en el sentido del contrato hashcode / hashcode ) es que la estructura de datos de la tabla hash se romperá.

  • Según los detalles exactos de la mutación y el estado de la tabla hash, una o ambas instancias se volverán invisibles para la búsqueda (por ejemplo, contains y otras operaciones). O está en la cadena hash incorrecta, o porque la otra instancia aparece antes en la cadena hash. Y es difícil predecir qué instancia será visible … y si seguirá siendo visible.

  • Si itera el conjunto, ambas instancias seguirán presentes … en violación del contrato Set .

Por supuesto, esto está muy roto desde la perspectiva de la aplicación.


Puede evitar este problema ya sea:

  • usando un tipo inmutable para tus elementos establecidos,
  • hacer una copia de los objetos a medida que los coloca en el conjunto y / o sacarlos del conjunto,
  • escribiendo su código para que “sepa” que no cambiará los objetos por la duración …

Desde la perspectiva de la corrección y robustez, la primera opción es claramente la mejor.


A propósito, sería realmente difícil “arreglarlo” de una manera general. No existe un mecanismo omnipresente en Java para saber … o para ser notificado … que algún elemento ha cambiado. Puede implementar dicho mecanismo clase por clase, pero debe codificarse explícitamente (y no será barato). Incluso si tuvieras ese mecanismo, ¿qué harías? Claramente, uno de los objetos ahora debería eliminarse del conjunto … ¿pero cuál?

Tiene razón y no creo que haya ninguna forma de protegerse contra el caso que analiza. Todas las colecciones que usan hash e iguales están sujetas a este problema. La colección no tiene ninguna notificación de que el objeto ha cambiado desde que se agregó a la colección. Creo que la solución que describes es buena.

Si está tan preocupado con este problema, quizás necesite replantear sus estructuras de datos. Podría usar objetos inmutables, por ejemplo. Con objetos inmutables no tendrías este problema.

HashSet no tiene conocimiento de las propiedades de sus miembros que cambian después de que se haya agregado el objeto. Si esto es un problema para usted, entonces puede considerar la posibilidad de hacer que GraphEdge inmutable. Por ejemplo:

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

En el caso donde GraphEdge es inmutable, cambiar el resultado de un valor al devolver una nueva instancia en lugar de cambiar la instancia existente.

Objects.hashCode está destinado a ser usado para generar un hascode usando objetos de parámetros. Lo está usando como parte del cálculo del código de acceso.

Intente reemplazar su implementación de hashCode con lo siguiente:

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

Tendrá que hacer la detección única en el momento en que itere su lista. Hacer un nuevo HashSet puede no parecer el camino correcto, pero ¿por qué no probar esto … y tal vez no utilizar un HashSet para comenzar …

 public class TestIterator { public static void main(String[] args) { List list = new ArrayList(); list.add("1"); list.add("1"); list.add("2"); list.add("3"); for (String s : new UniqueIterator(list)) { System.out.println(s); } } } public class UniqueIterator implements Iterable { private Set hashSet = new HashSet(); public UniqueIterator(Iterable iterable) { for (T t : iterable) { hashSet.add(t); } } public Iterator iterator() { return hashSet.iterator(); } }