2011-01-17 33 views
17

que la clase siguiente:objetos mutables y hashCode

public class Member { 
private int x; 
private long y; 
private double d; 

public Member(int x, long y, double d) { 
    this.x = x; 
    this.y = y; 
    this.d = d; 
} 

@Override 
public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + x; 
    result = (int) (prime * result + y); 
    result = (int) (prime * result + Double.doubleToLongBits(d)); 
    return result; 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj instanceof Member) { 
     Member other = (Member) obj; 
     return other.x == x && other.y == y 
       && Double.compare(d, other.d) == 0; 
    } 
    return false; 
} 

public static void main(String[] args) { 
    Set<Member> test = new HashSet<Member>(); 
    Member b = new Member(1, 2, 3); 
    test.add(b); 
    System.out.println(b.hashCode()); 
    b.x = 0; 
    System.out.println(b.hashCode()); 
    Member first = test.iterator().next(); 
    System.out.println(test.contains(first)); 
    System.out.println(b.equals(first)); 
      System.out.println(test.add(first)); 

} 

}

Produce los siguientes resultados:
30814 29853 false true true

Debido a que el código hash depende del estado del objeto que pueda sin más tiempo recuperado correctamente, por lo que el control de contención falla. El HashSet ya no funciona correctamente. Una solución sería hacer que el miembro sea inmutable, pero ¿es esa la única solución? ¿Deberían todas las clases agregadas a HashSets ser inmutables? ¿Hay alguna otra forma de manejar la situación?

Atentamente.

+0

¿Por qué el compilador no hace cumplir una regla de no permitir objetos mutables como claves en tablas hash u objetos en conjuntos de hash? Esto me parece horrible. – ncmathsadist

Respuesta

26

objetos en hashsets deben ya sea inmutable, o es necesario ejercer disciplina en no cambiar ellos después de que han sido utilizados en una hashset (o HashMap).

En la práctica, rara vez encuentro que esto sea un problema. Rara vez me resulta necesario utilizar objetos complejos ya que las teclas son elementos establecidos, y cuando lo hago generalmente no es un problema simplemente no mutarlos. Por supuesto, si ha expuesto las referencias a otro código en este momento, puede ser más difícil.

+0

Para objetos de valor. Los objetos de referencia pueden ser tan mutables como desee. –

+0

@Tom Hawtin: ¿No sería inseguro también mutar objetos de referencia? En particular, hablo de mutaciones que cambiarán el código hash del objeto o lo convertirán en miembro de una clase de equivalencia diferente. – Brian

+2

@Brian: sospecho que Tom está hablando de tipos que no anulan hashCode o iguales. –

7

Sí. Mientras que el mantenimiento de su clase mutable, se puede calcular el código hash y los métodos iguales basada en valores inmutables de la clase (tal vez un identificador generado) a que se adhieran al contrato hashCode definido en la clase del objeto:

  • Siempre que se se invoca en el mismo objeto más de una vez durante una ejecución de una aplicación Java, el método hashCode debe devolver consistentemente el mismo entero, siempre que no se modifique ninguna información utilizada en comparaciones iguales en el objeto. Este entero no necesita ser consistente desde una ejecución de una aplicación hasta otra ejecución de la misma aplicación.

  • Si dos objetos son iguales de acuerdo con el método igual (Objeto), al llamar al método hashCode en cada uno de los dos objetos debe producir el mismo resultado entero.

  • No es necesario que si dos objetos son desiguales de acuerdo con el método igual (java.lang.Object), al llamar al método hashCode en cada uno de los dos objetos debe producir resultados enteros distintos. Sin embargo, el programador debe tener en cuenta que la producción de resultados enteros distintos para objetos desiguales puede mejorar el rendimiento de hashtables.

Dependiendo de su situación puede ser más fácil o no.

class Member { 
    private static long id = 0; 

    private long id = Member.id++; 
    // other members here... 


    public int hashCode() { return this.id; } 
    public boolean equals(Object o) { 
     if(this == o) { return true; } 
     if(o instanceOf Member) { return this.id == ((Member)o).id; } 
     return false; 
    } 
    ... 
} 

Si usted necesita un seguro para hilos atributo, puede considerar el uso: AtomicLong lugar, pero una vez más, depende de cómo se va a utilizar el objeto.

+0

El problema con este enfoque es que si inserto dos instancias diferentes en un HashSet (por ejemplo, nuevo miembro (1,2, 3)) se insertarán dos veces. Además, el método equals no funcionará como se esperaba. Pero gracias por señalar el contrato hashCode: "Cada vez que se invoca en el mismo objeto más de una vez durante una ejecución de una aplicación Java, el método hashCode debe devolver consistentemente el mismo entero, siempre que no se use información en comparaciones iguales en el objeto modificado." No lo leí con cuidado. – robert

+0

Bueno, eso fue solo una muestra, podrías crearlo así: 'public Member (int a, int b, int) {this.id = a * 31 + b * 31 + 31 *; } 'Si crea otro' nuevo miembro (1,2,3) 'calculará el mismo hash, ** pero ** si modifica uno de ellos, tendrá que sincronizar. Puede usar un método de registro/fábrica y crear objetos como: 'Miembro a = Miembro.nueva instancia (1,2,3); Miembro b = Member.newInstance (1,2,3); 'y crea la instancia solo una vez. – OscarRyz

0

En teoría (y más a menudo que no, prácticamente también) de su clase, ya sea:

  1. tiene una identidad inmutable natural que se puede inferir a partir de un subconjunto de sus campos, en cuyo caso podrá usar esos campos para generar el hashCode de.
  2. no tiene identidad natural, en cuyo caso no es necesario utilizar un Set para almacenarlos, también podría utilizar un List.
3

Jon Skeet ha enumerado todas las alternativas. En cuanto a por qué las llaves en un mapa o una Set no deben cambiar:

El contrato de un Conjunto implica que en cualquier momento, no hay dos objetos O1 y O2 de tal manera que

o1 != o2 && set.contains(o1) && set.contains(o2) && o1.equals(o2) 

¿Por que no se requiere es especialmente claro para un mapa. Del contrato de map.get():

Más formalmente, si este mapa contiene una asignación de una clave k a un valor tal que v(key==null ? k==null : key.equals(k)), a continuación, este método devuelve v, de lo contrario devuelve null. (Puede haber como máximo uno de esos mapas).

Ahora, si modifica una clave insertada en un mapa, puede hacer que sea igual a otra tecla ya insertada. Además, el mapa no puede saber que lo has hecho. Entonces, ¿qué debería hacer el mapa si luego hace map.get(key), donde key es igual a varias teclas en el mapa? No hay una forma intuitiva de definir lo que eso significaría, principalmente porque nuestra intuición para estos tipos de datos es el ideal matemático de conjuntos y mapeos, que no tienen que lidiar con cambiar claves, ya que sus claves son objetos matemáticos y, por lo tanto, inmutables.

-1

Nunca cambie 'campo hashable" después de poner en un recipiente a base de hash.

Como si (Miembro) registrado su número de teléfono (Member.x) en la página amarilla (contenedor basado hash), pero ha cambiado su número de , entonces nadie puede encontrar en la página amarilla más

2

Como ya se ha mencionado, se puede aceptar las siguientes tres soluciones:.

  1. usar objetos inmutables; aun cuando su clase es mutable, es posible utilizar identidades inmutables en su hashcode implem entation y equals comprobando, por ejemplo, un valor de ID-like.
  2. De forma similar a lo anterior, implemente add/remove para obtener un clon del objeto insertado, no la referencia real. HashSet no ofrece una función get (por ejemplo, para permitirle modificar el objeto más adelante); por lo tanto, estás seguro de que no existirán duplicados.
  3. disciplina ejercicio en no cambiar ellos después de que han sido utilizados, como sugiere @Jon Skeet

Pero, si por alguna razón que realmente necesita para modificar los objetos después de su inserción a una HashSet, es necesario encontrar una forma de "informar" a su Colección con los nuevos cambios.Para conseguir esta funcionalidad:

  1. Puede utilizar el patrón de diseño Observer, y extender HashSet para implementar la interfaz Observer. Sus objetos Member deben ser Observable y updateHashSet en cualquier setter u otro método que afecte a hashcode o equals.

Nota 1: Extendiendo 3, usando 4: podemos aceptar alteraciones, pero aquellas que no crean un objeto ya existente (por ejemplo, actualicé la identificación de un usuario, asignando una nueva ID, no configurándola en una existente uno). De lo contrario, debe considerar el escenario en el que un objeto se transforma de tal manera que ahora es igual a otro objeto que ya existe en el Set. Si acepta esta limitación, la cuarta sugerencia funcionará bien; de lo contrario, debe ser proactivo y definir una política para tales casos.

Nota 2: usted tiene que proporcionar ambos estados anteriores y actuales del objeto alterado en su aplicación update, porque hay que eliminar inicialmente el elemento más antiguo (por ejemplo, utilizar getClone() antes de establecer nuevos valores), a continuación, añadir el objeto con el nuevo estado. El siguiente fragmento es solo una implementación de ejemplo, necesita cambios en función de su política de agregar un duplicado.

@Override 
public void update(Observable newItem, Object oldItem) { 
    remove(oldItem); 
    if (add(newItem)) 
     newItem.addObserver(this); 
} 

he utilizado técnicas similares en proyectos, en los que requieren múltiples índices en una clase, por lo que puede mirar hacia arriba con O (1) para conjuntos de objetos que comparten una identidad común; imagínelo como un MultiKeymap de HashSets (esto es realmente útil, ya que puede intersecar/unir índices y trabajar de manera similar a la búsqueda tipo SQL). En tales casos, anoto métodos (generalmente setters) que deben activarse. Cambia-actualiza cada uno de los índices cuando ocurre un cambio significativo, por lo que los índices siempre se actualizan con los últimos estados.

Cuestiones relacionadas