Genéricos y Colecciones. Parte 1: Sobreescribiendo equals y hashCode
miércoles, septiembre 28, 2011 at 6:14AM
pplouis in certificacion, certificaciones, genericos y colecciones, java, ocjp, scjp

Los genéricos fueron una de las características principales en el lanzamiento de Java 5.

Con la salida de los genéricos cambio la forma en como podemos manejar colecciones en Java.

Una de las características que tienen las colecciones es el poder ordenar y buscar entre los elementos que forman parte del contenido.

Podemos establecer el criterio de búsqueda u ordenamiento para nuestras colecciones, pero para esto debemos sobrescribir los métodos equals y hashCode.

En esta primera publicación tocaré el tema de sobrescribir los métodos equals y hashCode, las características de cada uno de estos métodos y algunos ejemplos.

 

Puntos a tocar en esta publicación:

 

 

Sobrescribiendo el método equals

 

Indagando en la javadoc acerca del método equals(Object o), podemos encontrar lo siguiente:

 

Método

Valor de retorno

Descripción

equals(Object o)

boolean

Indica si un objeto es igual a este.

 

Este método lo utilizamos para saber si un objeto es igual que otro. Este método utiliza el operador == para comparar a dos objetos y decidir si son iguales, por ejemplo podemos tener la siguiente clase:

 

 

public class Demo {

 

private int boleta;

 

public Demo(int boleta){

this.boleta = boleta;

}

 

public int getBoleta(){

return this.boleta;

}

 

public static void main(String[] args) {

Demo demoA = new Demo(20);

Demo demoB = new Demo(20);

 

System.out.println(demoA.equals(demoB));

}

}

 

La ejecución del anterior método main da como resultado en consola false. Esto es porque el método equals sin sobrescribir ocupa el operador == para comparar a dos objetos. Pero supongamos que queremos diferenciar a los objetos mediante su atributo boleta, ya que es el identificador de un Alumno para una aplicación escolar. Para esto debemos sobrescribir el método equals, diciéndole qué propiedad del objeto debe ser comparada para determinar si un objeto es igual a otro. El método sobrescrito se vería de esta forma:

 

@Override

public boolean equals(Object o){

if(( o instanceof Demo) && (((Demo)o).getBoleta() == this.boleta))

{

return true;

}else{

return false;

}

}

 

la tercera linea es la que tienen la magia. En ella hacemos un par de validaciones, la primera tiene que ver con estar seguros de que el objeto o es una instancia de la clase Demo, y la segunda es verificar si la propiedad boleta de el objeto o es igual a la propiedad boleta del objeto que invoco el método equals. Si los valores de boleta son los mismos, el método equals que sobrescribimos regresara true. Con esto nosotros tomamos la decisión sobre que criterio se debe tomar para comparar a nuestros objetos.

 

public static void main(String[] args) {

Demo demoA = new Demo(20);

Demo demoB = new Demo(20);

 

System.out.println(demoA.equals(demoB));

}

 

Por lo anterior, esta ejecución del método main, nos dará como resultado un true en la salida por consola.

 

Hay tipos de colecciones como los Sets que no nos permiten agregar objetos duplicados a la colección. La forma en como los Sets van a decidir si un objeto esta duplicado o no, la especificamos nosotros cuando sobrescribimos el método equals. Lo mismo cuando en una colección de tipo Hash* queremos buscar un elemento en especifico, la colección sabe que objeto regresar, dado el criterio que nosotros definimos al sobrescribir el método equals y hashCode. En esto radica la importancia de sobrescribir estos métodos, siempre y cuando este dentro de nuestras intenciones contar con este tipo de prestaciones.

 

 

Ojo, mucho ojo

 

Asegúrate de sobrescribir el método equals. Las siguientes son implementaciones del método equals que son validas para el compilador, pero no válidas para sobrescribir el método:

  1. boolean equals(Objeto o). Esta implementación no sobreescribe el método equals de la clase Object, ya que el método debe ser declarado como public.
  2. public boolean equals(Demo o). Esta implementación no sobreescribe el método equals de la clase Object, ya que el parámetro que necesita el método equals debe ser explícitamente un objeto de la clase Object, y no uno que extienda de éste. Esta implementación, al igual que la anterior es una sobrecarga del método equals, mas no sobreescribe este método. 

 

public boolean equals(Objeto o);

 

 

 

 

Sobrescribiendo el método hashCode

 

¿Qué implica el hashcode?

 

Algunas colecciones usan el valor hashcode para ordenar y localizar a los objetos que están contenidos dentro de ellas. El hashcode es un numero entero, sin signo, que sirve en colecciones de tipo Hash* para un mejor funcionamiento en cuanto a performance. Este método debe ser sobrescrito en todas las clases que sobrescriban el método equals, si no se quiere tener un comportamiento extraño al utilizar las colecciones de tipo Hash* y otras clases. Si dos objetos son iguales según el método equals sobrescrito, estos deberian regresar el mismo hashcode. Véase el siguiente ejemplo:

 

 

public class Demo {

 

private int boleta;

 

public Demo(int boleta){

this.boleta = boleta;

}

 

public int getBoleta(){

return this.boleta;

}

 

@Override

public boolean equals(Object o){

if((o instanceof Demo) && (((Demo)o).getBoleta()== this.boleta))

{

return true;

}else{

return false;

}

}

 

public static void main(String[] args) {

Demo demoA = new Demo(20);

Demo demoB = new Demo(20);

 

System.out.println(demoA.equals(demoB));

System.out.println(demoA.hashCode());

System.out.println(demoB.hashCode());

}

}

 

Esto nos da como resultado:

 

true

1414159026

1569228633

 

regresa true porque estos objetos son iguales, debido a que se sobrescribió el método equals. Si son considerados iguales por equals, esto se reflejará al utilizarse en las colecciones de tipo Hash*. Lo que podemos ver en la salida de este código es que a pesar de que ambos objetos son iguales, el hashcode no es igual. Esto es porque no hemos sobrescrito el método hashCode.

 

El uso principal del hashcode, es como lo mencionamos arriba, cuando se manejan colecciones de tipo Hash*. La forma en como operan las colecciones de este tipo es a grandes rasgos la siguiente: Las colecciones de tipo Hash* almacenan los objetos en lugares llamados baldes, de acuerdo al numero obtenido por el método hashCode. Si el método hashCode regresa un 150, el objeto será guardado en el balde numero 150. Puede llegar a pasar que haya mas de un objeto de diferente tipo en el mismo balde. Esto no ocasiona ningún problema al momento de recuperar el objeto del balde, ya que al buscarlo este tipo de colecciones necesita como parámetro un objeto con el mismo valor hashcode, el cual utilizara para buscar el numero de balde que contiene a el objeto en cuestión. Si hay mas de un objeto, el siguiente criterio para determinar cual es el objeto buscado, es la utilización del método equals. Así es como este tipo de colecciones para buscar un objeto, ya sea para regresarlo y para ordenarlo. Lo cual falla si no sobrescribimos el método hashCode.

 

 

Sobrescribiendo el método hashCode

 

A continuación veremos como podremos sobrescribirlo:

 

 

@Override

public int hashCode() {

int hash = 7;

hash = 97 * hash + this.boleta;

return hash;

}

 

La salida que obtenemos una vez que se rescribe este método en el ejemplo de arriba es :

 

true

699

699

 

lo que nos dice que estos métodos son iguales según equals, y que además tienen el mismo numero hashcode.

 

Este método hashCode regresara consistentemente el mismo valor, siempre y cuando el campo boleta no cambie. Cada desarrollador puede implementar de diferente manera igualmente validas, correctas y eficientes este método. Lo que debemos de tener en mente al sobrescribir este método es que si para sobrescribir el método utilizamos variables de instancia (en nuestro ejemplo boleta), también debemos utilizar variables de instancia para generar un hashcode correcto. En el caso de las constantes que se utilizan en el ejemplo de arriba, se recomienda utilizar números primos, para una mejor distribución del hashcode generado.

 

Reglas que sigue el método hashCode

 

 

Ojo, mucho ojo

 

Con esto concluimos que una de las principales utilidades que tiene el sobreescribir estos métodos en nuestras aplicaciones se da cuando utilizamos colecciones. Hay ciertas reglas a seguir para poder sobreescribir correctamente ambos metodos. Y tambien que estos metodos tienen propiedades, las cuales nos ayudan a entender su naturaleza y funcionalidad.

Preguntas habituales en el examen de certificación sobre este punto.

Las preguntas que pueden caer en el examen con respecto a este punto pueden ser parecidas a las siguientes:

1) ¿Qué condiciones deben cumplir un método equals para tener validez?

2) ¿Cuál es la firma de la función del método equals para ser sobre escrito? (En este caso, es importante conocer que equals tiene la definición de public boolean equals (Object o)).

3) ¿Qué ventajas proporciona el uso de redefinir equals? La respuesta es que nos permite poder conocer si dos instancias de clase con iguales, y con ello, nos permite hacer uso, por ejemplo, de colecciones donde no se van a poder permitir introducir repetidos.

4) ¿En caso de no redefinir el método equals, qué se está comparando realmente? En el caso de no hacer una sobreescritura del método equals, lo que sucede es que se comparan las posiciones de memoria de un objeto, no su contenido. Es decir, si no sobreescribimos equals y tenemos un objeto parecido a lo siguiente: Jugador j1 = new Jugador("Messi"), y un segundo objeto, Jugador j2 = new Jugador("Messi"), y realizamos la acción j1.equals(j2) devolverá falso porque no son la misma instancia, sin embargo, j1.equals(j1) devolverá cierto.

5) ¿Qué condiciones tiene que cumplir la firma de un método hashcode?

6) ¿Qué ventajas proporciona la redefinición del método hashcode? Las ventajas que proporciona la redefinición de este método es que permite hacer uso de una manera más eficiente de los objetos de tipo colección que hagan uso del algoritmo hash, y con ello, ganar rendimiento.

7) ¿Cuál es la firma del método hashcode?

Article originally appeared on javaHispano (http://www.javahispano.org/).
See website for complete article licensing information.