¿Qué cuestiones se deben considerar al anular iguales y hashCode en Java?

Resuelto Matt Sheppard asked hace 16 años • 11 respuestas

¿Qué problemas/obstáculos se deben considerar al anular equalsy hashCode?

Matt Sheppard avatar Aug 26 '08 15:08 Matt Sheppard
Aceptado

La teoría (para los lingüistas y los aficionados a las matemáticas):

equals()( javadoc ) debe definir una relación de equivalencia (debe ser reflexiva , simétrica y transitiva ). Además, debe ser consistente (si los objetos no se modifican, entonces debe seguir devolviendo el mismo valor). Además, o.equals(null)siempre debe devolver falso.

hashCode()( javadoc ) también debe ser consistente (si el objeto no se modifica en términos deequals() , debe seguir devolviendo el mismo valor).

La relación entre los dos métodos es:

Siempre a.equals(b), entonces a.hashCode()debe ser igual que b.hashCode().

En la práctica:

Si anula uno, entonces debería anular el otro.

Utilice el mismo conjunto de campos que utiliza para calcular equals()para calcularhashCode() .

Utilice las excelentes clases auxiliares EqualsBuilder y HashCodeBuilder de la biblioteca Apache Commons Lang . Un ejemplo:

public class Person {
    private String name;
    private int age;
    // ...

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
            // if deriving: appendSuper(super.hashCode()).
            append(name).
            append(age).
            toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
       if (!(obj instanceof Person))
            return false;
        if (obj == this)
            return true;

        Person rhs = (Person) obj;
        return new EqualsBuilder().
            // if deriving: appendSuper(super.equals(obj)).
            append(name, rhs.name).
            append(age, rhs.age).
            isEquals();
    }
}

Recuerda también:

Cuando utilice una colección o un mapa basado en hash, como HashSet , LinkedHashSet , HashMap , Hashtable o WeakHashMap , asegúrese de que el hashCode() de los objetos clave que coloque en la colección nunca cambie mientras el objeto esté en la colección. La forma infalible de garantizar esto es hacer que las claves sean inmutables, lo que también tiene otros beneficios .

Antti Kissaniemi avatar Aug 26 '2008 09:08 Antti Kissaniemi

Hay algunos problemas que vale la pena notar si estás tratando con clases que persisten usando un Mapeador de relaciones de objetos (ORM) como Hibernate, ¡si no pensabas que esto ya era irrazonablemente complicado!

Los objetos cargados de forma diferida son subclases

Si sus objetos persisten mediante un ORM, en muchos casos tendrá que tratar con servidores proxy dinámicos para evitar cargar objetos demasiado pronto desde el almacén de datos. Estos servidores proxy se implementan como subclases de su propia clase. Esto significa que this.getClass() == o.getClass()volverá false. Por ejemplo:

Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy

Si se trata de un ORM, el uso o instanceof Persones lo único que se comportará correctamente.

Los objetos cargados de forma diferida tienen campos nulos

Los ORM suelen utilizar captadores para forzar la carga de objetos cargados de forma diferida. Esto significa que person.nameserá nullsi personse carga de forma diferida, incluso si person.getName()fuerza la carga y devuelve "John Doe". En mi experiencia, esto surge con más frecuencia en hashCode()y equals().

Si está tratando con un ORM, asegúrese de utilizar siempre captadores y nunca incluir referencias en hashCode()y equals().

Guardar un objeto cambiará su estado

Los objetos persistentes suelen utilizar un idcampo para contener la clave del objeto. Este campo se actualizará automáticamente cuando se guarde un objeto por primera vez. No utilice un campo de identificación en hashCode(). Pero puedes usarlo en equals().

Un patrón que uso a menudo es

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

Pero: no puedes incluirlo getId()en hashCode(). Si lo hace, cuando un objeto persiste, cambia hashCode. Si el objeto está en un HashSet, "nunca" lo volverás a encontrar.

En mi Personejemplo, probablemente usaría getName()for hashCodey getId()plus getName()(solo por paranoia) for equals(). Está bien si existe algún riesgo de "colisiones" para hashCode(), pero nunca está bien para equals().

hashCode()debe utilizar el subconjunto de propiedades que no cambian deequals()

Johannes Brodwall avatar Nov 02 '2008 02:11 Johannes Brodwall