Pregunta

En Java, tengo una subclase Vertex de la clase Java3D Point3f . Ahora Point3f calcula equals () en función de los valores de sus coordenadas, pero para mi clase Vertex quiero ser más estricto: dos vértices son solo igual si son el mismo objeto. Hasta ahora, todo bien:

class Vertex extends Point3f {

    // ...

    public boolean equals(Object other) {
        return this == other;
    }
}

Sé que esto viola el contrato de equals () , pero como solo compararé vértices con otros vértices, esto no es un problema.

Ahora, para poder poner vértices en un HashMap , el método hashCode () debe devolver resultados consistentes con equals () . Actualmente lo hace, pero probablemente basa su valor de retorno en los campos del Point3f , y por lo tanto dará colisiones hash para diferentes objetos Vertex con las mismas coordenadas.

Por lo tanto, me gustaría basar el hashCode () en la dirección del objeto, en lugar de calcularlo desde los campos Vertex . Sé que la clase Object hace esto, pero no puedo llamar a su método hashCode () porque Point3f lo anula.

Entonces, en realidad mi pregunta es doble:

  • ¿Debería incluso desear un equals () tan bajo?
  • En caso afirmativo, ¿cómo obtengo la dirección del objeto para calcular el código hash?

Editar: Acabo de pensar en algo ... podría generar un valor aleatorio int en la creación de objetos, y usarlo para el código hash. ¿Es eso una buena idea? ¿Por qué (no)?

¿Fue útil?

Solución

Use System.identityHashCode () o use un IdentityHashMap.

Otros consejos

System.identityHashCode () devuelve el mismo código hash para el objeto dado que el método predeterminado hashCode () , independientemente de si la clase del objeto dado anula hashCode () .

Utiliza un delegado a pesar de que esta responde es probablemente mejor.


class Vertex extends Point3f{
   private final Object equalsDelegate = new Object();
   public boolean equals(Object vertex){
      if(vertex instanceof Vertex){
         return this.equalsDelegate.equals(((Vertex)vertex).equalsDelegate);
      }
      else{
         return super.equals(vertex);
      }
   }
   public int hashCode(){
      return this.equalsDelegate.hashCode();
   }
}

Solo para su información, su método de igualdad NO viola el contrato de igualdad (para el contrato del Objeto base que es) ... ese es básicamente el método de igualdad para el método de Objeto base, así que si quiere identidad es igual en lugar de Vertex es igual , eso está bien.

En cuanto al código hash, realmente no necesita cambiarlo, aunque la respuesta aceptada es una buena opción y será mucho más eficiente si su tabla hash contiene muchas claves de vértice que tienen los mismos valores.

La razón por la que no necesita cambiarlo es porque está completamente bien que el código hash devuelva el mismo valor para los objetos que es igual a devuelve false ... incluso es un código hash válido para devolver 0 todos los tiempo para CADA instancia. Si esto es eficiente para las tablas hash es un problema completamente diferente ... obtendrá muchas más colisiones si muchos de sus objetos tienen el mismo código hash (lo que puede ser el caso si deja el código hash solo y tiene muchos vértices con los mismos valores).

Por favor, no acepte esto como respuesta, por supuesto (lo que eligió es mucho más práctico), solo quería darle un poco más de información de fondo sobre códigos hash e iguales ;-)

¿Por qué quieres anular hashCode () en primer lugar? Desea hacerlo si desea trabajar con alguna otra definición de igualdad. Por ejemplo

clase pública A {   int id;

public boolean equals (A other) {return other.id == id}   public int hashCode () {return id;}

} donde desea dejar en claro que si las identificaciones son las mismas, entonces los objetos son los mismos, y anula el código hash para que no pueda hacer esto:

HashSet hash = nuevo HashSet (); hash.add (nuevo A (1)); hash.add (nuevo A (1)); y obtenga 2 A idénticas (desde el punto de vista de su definición de igualdad). El comportamiento correcto sería que solo tendría 1 objeto en el hash, la segunda escritura sobrescribiría.

Dado que no está utilizando iguales como comparación lógica, sino física (es decir, es el mismo objeto), la única forma de garantizar que el código hash devolverá un valor único es implementar una variación propia. sugerencia. En lugar de generar un número aleatorio, use UUID para generar un valor único real para cada objeto.

El System.identityHashCode () funcionará, la mayoría de las veces, pero no está garantizado ya que el método Object.hashCode () no está garantizado para devolver un único valor para cada objeto. He visto suceder el caso marginal, y probablemente dependerá de la implementación de la VM, que no es algo de lo que querrá que dependa su código.

Extracto de los javadocs para Object.hashCode (): Por mucho que sea razonablemente práctico, el método hashCode definido por la clase Object devuelve enteros distintos para objetos distintos. (Esto normalmente se implementa convirtiendo la dirección interna del objeto en un número entero, pero el lenguaje de programación JavaTM no requiere esta técnica de implementación).

El problema que aborda este problema es el caso de que dos objetos de punto separados se sobrescriban entre sí cuando se insertan en el mapa hash porque ambos tienen el mismo hash. Como no hay iguales lógicos, con la anulación de hashCode (), el método identityHashCode puede hacer que este escenario ocurra. Cuando el caso lógico solo reemplazaría las entradas hash para el mismo punto lógico, el uso del hash basado en el sistema puede hacer que ocurra con dos objetos, la igualdad (e incluso la clase) ya no es un factor.

La función hashCode () se hereda de Object y funciona exactamente como lo desea (a nivel de objeto, no a nivel de coordenadas). No debería haber necesidad de cambiarlo.

En cuanto a su método igual, no hay ninguna razón para usarlo, ya que puede hacer obj1 == obj2 en su código en lugar de usar igual, ya que está destinado a ordenar y similar, donde comparar coordenadas hace mucho más sentido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top