sábado, 3 de diciembre de 2011

HashCode( ) y equals( )

votar
   Hoy vamos a hablar de dos métodos que están muy relacionados: equals( ) y hashCode( ). Estos dos métodos están en la clase Object, y como esta clase es la madre de todas clases, todas ellas heredan esto métodos. Por supuesto, como ya vimos en otro post, estos métodos pueden ser overriden (sobreescritos o sobrepasados).

   Estos métodos son muy utilizados al usar colecciones (ya hablaremos de ellas en otra entrada) pero hay que utilizarlos bien y saber cómo sobreescribirlos para no meter la pata.

   Expliquemos en qué consisten. El método equals( ) compara dos objetos y devuelve true si son iguales. Utiliza el operador (= =). ¡No confundir nunca con (=) que asigna un valor!. Cuando compara dos tipos primitivos es sencillo: 

   2300 = = 2300;
   2´575 = = 2´575;

   pero cuando comparamos objetos la cosa cambia, porque lo que equals( ) compara son las referencias a los objetos y no los mismos objetos, y si las referencias no son iguales considera que los objetos tampoco, aunque sean significativamente equivalentes. Lo entenderemos mejor explicando este ejemplo que podéis encontrar en el tutorial de Oracle.

   Supongamos que hay dos ejemplares de un libro:

   Libro primerEjemplar = new Libro( );
   Libro segundoEjemplar = new Libro( );

   Aunque sean dos ejemplares del mismo libro, y, por lo tanto, equivalentes para nosotros, el método equals( ) los tratará como distitntos a no ser que lo sobreescribamos o sobrepasemos. Ahora bien, los libros tienen un número de identificación, ISBN, que es igual para todos los ejemplares del mismo libro. Usando este número como referencia, podemos sobreescribir equals( )

   public class Libro{
      public boolean equals(Object o){
         if(o instanceof Libro) //nos aseguramos de que el objeto en cuestión es un libro
            return ISBN.equals((Libro)o.getISBN( )); //hacemos un casting para poder usar el nuevo método     equals()
              else
                return false;
      }
   }

   Veamos ahora cómo quedaría:
   
   Libro primerEjemplar = new Libro("0201914670"); //el mismo número que el ejemplo del tutorial
   Libro segundoEjemplar = new Libro("0201914670");
      if(primerEjemplar.equals(segundoEjemplar)){
         System.out.println("Los dos objetos son iguales");
      }else{
         System.out.println("Los objetos no son iguales");
      }
   
   Ahora, los dos ejemplares del mismo libro son considerados iguales porque lo que se compara es su ISBN.

   El método equals( ) tiene un contrato o normas que hay que cumplir al utilizarlo. Son las siguientes, y no, no estás en clase de matemáticas:

  • Es reflexivo. Para cualquier valor x, x.equals(x) debe devolver true.
  • Es simétrico. Para cualesquiera valores x e y, x.equals(y) es cierto si y sólo si y.equals(x) devuelve true.
  • Es transitivo. Para cualesquiera valores x, y y z, si x.equals(y) devuelve true e y.equals(z) devuelve true, entonces x.equals(z) debe devolver true.
  • Es consistente. Para cualesquiera valores x e y, x.equals(y) debe devolver consistentemente true o false mientras no se modifique ningún dato utilizado en la comparación.
  • Para cualquier valor x no vacío, x.equals(null) debe devolver falso.


   Además, si sobreescribes el método equals( ), también debes sobreescribir el método hashCode( ) ¿Por qué?.
   
   El valor que devuelve el método hashCode( ) es la alocación en la memoria del objeto, expresada en un número hexadecimal. (Excepto los supergeekies, este método normalmente se sobreescribe para que devuelva algo más comprensible al común de los mortales). Esto significa que, antes de sobreescribir el método equals( ), dos referencias a un objeto son iguales cuando el objeto es el mismo y por tanto ocupa el mismo lugar en la memoria. Pero al sobreescribir el método equals( ), vimos antes que estamos asignando la misma referencia a dos ejemplares distintos de un mismo libro. Así que el método hashCode( ) ya no es válido a no ser que lo sobreescribamos también, porque la regla básica es que si dos objetos son iguales según equals( ), su hashCode debe ser también igual.

   El hashCode viene siendo, pues, como el número de identidad de un objeto. Pero varios objetos pueden tener el mismo número de identidad. Incluso objetos distintos según equals( ).

   Imaginemos una cajonera donde en un cajón guardamos sólo bolígrafos, en otro sólo lápices, en otro sólo papel, etc. Cuando queremos un bolígrafo, sabemos perfectamente a cuál cajón ir. Ése es su hashCode. Pero resulta que no queremos un bolígrafo cualquiera, si no uno con tinta verde. Ahí es donde usamos el método equals( ). Vemos que todos los bolígrafos tienen el mismo hashCode, pero no todos son iguales. Eso sí, si dos objetos son bolígrafos, se guardan en el mismo cajón.

   HashCode( ) también tiene un contrato, que es como sigue:
  • Cuando durante la ejecución de una aplicación se invoque más de una vez el método hashCode( ), éste debe devolver consistentemente el mismo número, si no hay ningún cambio en equals( ). En distintas ejecuciones de la misma aplicación, el número puede variar.
  • Si dos objetos son iguales según equals( ), su hashCode es el mismo.
  • Si dos objetos son distintos según equals( ), su hashCode puede ser distinto o no.

   Claro, es mucho más eficiente a la hora de encontrar el bolígrafo deseado si estuvieran clasificados en distintos cajones según el color, pero a veces no tenemos tantos cajones, y otras veces nos da igual el color...como ya veremos al hablar de colecciones.

   Los que os sintáis un poco superados por este tema, podéis leer el libro de Kathy Sierra y Bert Bates "SCJP Sun Certified Programmer for Java 6". Los que queráis ampliarlo tenéis "Effective Java", de J. Bloch.
Y por supuesto, no olvidéis visitar Java Para Nulos ; )

   

2 comentarios:

  1. Hola Sonia
    Felicidades por este gran artículo!, tu explicación es majestuosa, me han quedado muy claros los conceptos y la estrecha relación que hay entre ambos.
    Gracias por el esfuerzo de tu trabajo y compartir tus conocimientos, aunque yo obtuve esta duda para aprender algo en C#, veo que se refiere a lo mismo en Java.
    Saludos :)

    ResponderEliminar
  2. Muchísimas gracias por tu amable comentario, Jose.
    En realidad, lo mío no tiene mucho mérito: me limito a intentar explicar lo que otros me han enseñado.
    Y es estupendo que esto te haya servido para C#. Lo bueno de la programación es que todos nos entendemos bastante bien, sea cual sea el lenguaje que usemos.
    ¡Saludos! :-)

    ResponderEliminar