El Blog de Ana Buigues » equals http://anabuigues.com Wed, 07 Dec 2011 16:07:47 +0000 es-ES hourly 1 http://wordpress.org/?v=3.4 Cómo sobreescribir los métodos equals y hashCode de Java http://anabuigues.com/2010/07/06/como-sobreescribir-los-metodos-equals-y-hashcode-de-java/ http://anabuigues.com/2010/07/06/como-sobreescribir-los-metodos-equals-y-hashcode-de-java/#comments Tue, 06 Jul 2010 20:18:23 +0000 Ana Buigues http://anabuigues.com/?p=1051 En la clase java.lang.Object (y por lo tanto, por herencia, en todas las demás clases) tenemos métodos que a veces olvidamos y que son importantes:

  • public boolean equals(Object o)
  • public int hashCode()

Estos métodos son especialmente importantes si vamos a guardar nuestros objetos en cualquier tipo de colección: listas, mapas… y más aun si los objetos que vamos a guardar en la colección son serializables.

Estos métodos tienen formas explicitas de cómo hay que implementarlos. Sobreescribir estos métodos puede parecer simple, pero en realidad hay muchas formas de hacerlo incorrectamente lo que nos puede llevar a muchos crebraderos de cabeza. Lo vemos a continuación.

Sobreescribir el método equals

Cuando sobreescribimos el método equals tenemos que tener en cuenta lo que se especifica en el API de Java para Object sobre este método. Debe cumplir las siguientes propiedades:

  • Reflexiva: para cualquier referencia no nula para un valor x, x.equals(x) debe devolver true.
  • Simétrica: para cualquier referencia no nula para valores x e y, x.equals(y) debe devolver true sii y.equals(x) devuelve true.
  • Transitiva: para cualquier referencia no nula para valores x, y,z, si x.equals(y) devuelve true y y.equals(z) devuelve true, entonces x.equals(z) debe devolver true.
  • Consistente: para cualquier referencia no nula para valores x e y, múltiples llamadas al método x.equals(y) deben consistentemente devolver siempre true o consistentemente devolver false. Siempre y cuando no se modifique la información usada en las comparaciones.
  • No nulo: para cualquier referencia no nula para un valor x, x.equals(null) debe devolver false.

Ahora que sabemos lo que tiene que cumplir, vamos a ver que pasos podemos seguir para su implementación:

  • Usamos el operador == para comprobar si el argumento es una referencia al mismo objeto.
  • Usamos el operador instanceof para comprobar si el argumento es un objeto de nuestra clase.
  • Hacemos un cast del argumento al nuestro objeto. Ya sabemos que es una instancia de nuestro objeto, por el paso anterior.
  • Para cada campo significativo de nuestro objeto, comprobamos que se corresponda con el que se pasa como argumento. Primero comprobamos los tipos primitivos y luego los más complejos. Para los tipos Float y Double, usamos los métodos Float.Compare y Double.Compare. Para el tipo String, usamos el equals del string. Para comparar arrays usamos Arrays.equals. Tenemos que tener en cuenta que los campos pueden contener referencias a null. No debemos incluir campos que tengan que ver con el estado de un objeto, como por ejemplo tipos Lock. Tampoco tenemos que incluir campos que sean cálculos de otros campos, es redundante.
  • Cuando terminemos, tenemos que preguntarnos: ¿es reflexivo?, ¿es simétrico?, ¿es transitivo?, ¿es consistente? y ¿no nullo?
//Ejemplo de cómo sobreescribir el método equals()
public class Casa {
 private int num;
 private String direccion;
 private double precio;
 private List<Propietario> prop;
 public Casa(int num, String direccion, double precio,
            List<Propietario> prop) {
  this.num = num;
  this.direccion = direccion;
  this.precio = precio;
  this.prop = prop;
 }
 @Override
 public boolean equals(Object o) {
  if (o == null)
   return false;
  if (o == this)
   return true;
  if (!(o instanceof Casa))
   return false;
  Casa c = (Casa) o;
  if (num != c.num)
   return false;
  if (direccion == null || !direccion.equals(c.direccion))
   return false;
  if (Double.compare(precio, c.precio) != 0)
   return false;
  if (prop != c.prop && (prop == null || !prop.equals(c.prop)))
   return false;
 return true;
 }
}
public class Propietario {
 private String nombre;
 private String apellidos;
 private int num;
 public Propietario(String nombre, String apellidos, int num) {
  this.nombre = nombre;
  this.apellidos = apellidos;
  this.num = num;
 }
 @Override
 public boolean equals(Object o) {
  if (o == null)
   return false;
  if (o == this)
   return true;
  if (!(o instanceof Propietario))
   return false;
  Propietario p = (Propietario) o;
  if ((nombre == null) ? (p.nombre != null) : !nombre.equals(p.nombre))
    return false;
  if ((apellidos == null) ? (p.apellidos != null) :
     !apellidos.equals(p.apellidos))
    return false;
  if (num != p.num)
    return false;
  return true;
 }
}

Sobreescribir el método hashCode

Siempre que sobreescribamos el método equals, también tenemos que sobreescribir también el método hashCode. En el API de java para Object del método hashCode se especifica lo siguiente:

  • Cuando este método es invocado sobre el mismo objeto una o más veces durante una ejecución en una aplicación, el hashCode debe de ser consistente devolviendo siempre el mismo valor, siempre que no se modifique el objeto. Este valor no tiene que ser consistente entre ejecuciones distintas de la aplicación.
  • Si dos objetos son iguales segun el método equals, entonces el hashCode de los dos objetos tiene que ser el mismo.
  • Si dos objetos no son iguales, el hashCode no tiene que ser necesariamente distinto, pero es recomendable que lo sea.

Unos pasos para implementar un buen método hashCode son:

  • Declaramos una variable entera y le asignamos un número, por ejemplo result=17.
  • Para cada campo significativo de nuestro objeto, f:
    • Calculamos en int c el valor de:
      • Tipo boolean: hacemos (f?1 : 0)
      • Tipos byte, char, short, o int: hacemos (int) f
      • Tipo Long: hacemos (int)(f^(f>>>32))
      • Tipo Float: hacemos Float.doubleToIntBits(f)
      • Tipo Double: hacemos Double.doubleToLongBits(f) (int)(f^(f>>>32))
      • Si es una referencia a un objeto, llamamos al hashCode del objeto. Si la referencia es nula, devolvemos un 0.
      • Si es un Array, utilizamos el método Arrays.hashCode.
    • Acumulamos el valor de c en result : result = 31 * result + c
  • Devolvemos el valor de result

Podemos excluir los campos que no comprobemos en el método equals, pero no es recomendable.

//Ejemplo de cómo sobreescribir el método hashCode
@Override
public int hashCode() {
  int result = 17;
  result = 31 * result + num;
  result = 31 * result + (direccion != null ?
           direccion.hashCode() : 0);
  result = 31 * result + (int) (Double.doubleToLongBits(precio)
           ^((Double.doubleToLongBits(precio) >>> 32));
  result = 31 * result + (propietarios != null) ?
           propietarios.hashCode() : 0);
 return result;
 }

Fuente: Libro Effective Java de Joshua Bloch

]]>
http://anabuigues.com/2010/07/06/como-sobreescribir-los-metodos-equals-y-hashcode-de-java/feed/ 14