martes, junio 24, 2008

Equals en java

Mirando las estadísticas del blog (sí, hace tiempo puse un contador de google-analytics, me hace mucha ilusión ver cuantas visitas tienen cada semana) me salen que la mayoría de los que llegan al blog son a través de buscadores, por lo que los pocos que seáis habituales, gracias, no sabéis la ilusión que me hace (y lo que me sube el ego :D ).
El caso es que las visitas más recurrentes van hacia el post de Equals y HashCode y el de java.lang.OutOfMemoryException : PermGem, a parte de que las búsquedas relacionadas con Portlets y Liferay.
¿Porque cuento esto? porque revisando el post me doy cuenta que fue uno de los primeros que escribí, y lo hice más que nada para probar mi javascript de "coloreado de sintaxis" y el CSS y resulta que es ahora uno de los más vistos. Por lo que me siento en parte responsable del tema y he decidido ampliarlo un poco.
Lo primero es hablar del método equals y que significa. A diferencia de otros lenguajes, en Java el símbolo de igualdad (== ) en objetos indica "la misma instancia", no que sus valores sean iguales. Por eso los de Sun inventaron un concepto de "objetos iguales" que no lo mismo que "el mismo objeto". Esta diferencia se hace con el método "equals".
// Aqui tenemos dos clases String
String uno = new String ("Un texto");
String dos = new String ("Un texto");
if (uno == dos ) {// Esta comparacion siempre devuelve false
  ... Esto no se ejecutará
}
if (uno.equals(dos)) {// Esta comparación si devuelve true
 .... Esto si se ejecutará.
}
Por defecto, el objeto padre "Object" tiene implementado el método "equals" como una "igualdad de instancia", por lo que si no sobrescribimos para nuestra clase, "equals" y "==" tienen el mismo resultado.
Para cada caso, el método equals deberá de ser diferente, por ejemplo :
class Punto {
int x;
int y;

// el metodo equals seria :
public boolean equals (Object obj) {
  if (this == obj) {
    return true;
  }
  if (obj instanceof Punto) {
    Punto otro = (Punto)obj;
    return ((x==otro.x) && (y==otro.y))
  } else {
     return false;
  }
}
}
Como se ve ahora dos puntos son iguales si sus coordenadas son las mismas. ¿Se entiende mejor?.
Otro concepto a añadir con el método Equals es el "hashCode". Este método se emplea en las colecciones para poder localizar los elementos dentro de esta. Para ello se incluye en la documentación de Sun una nota :
dos elementos iguales deben de tener el mismo hashCode, siendo esta una propiedad transitiva. Por lo tanto dos objetos con el mismo hashCode no tienen que ser obligatoriamente iguales
De esto se deduce que cuando se sobrescribe el método equals es necesario sobrescribir el método HashCode. Para nuestro ejemplo podríamos hacer:
public int hashCode () {
 // Multiplicamos por numeros primos
 int result = 17;
 result = 37 * result + x;
 result = 37 * result + y;
 return result;
}
Ahora ya tenemos nuestra clase preparada para poder usar el equals correctamente.
Antes de terminar una breve anotación, hacer esta tarea casi siempre es algo pesado y bastante tedioso, por lo que recomiendo usar algunas soluciones más cómodas. La más rápida de encontrar es usar las librerías "commons-lang" de apache. En esta, encontramos unas clases útiles : EqualsBuilder y HashCodeBuilder que permiten "automatizar" el proceso de creación de métodos equals y hashCode.
También existe por ahí algún plugin para eclipse, pero no he sido capaz de volver a encontrarlo.

11 comentarios:

Alberto dijo...

ehhhhhhhhhhh!!!!!!!

Yo soy uno de esos incondicionales que semanalmente te suben el ego :)

Saludos y sigue así, me resultan muy interesantes los post sobre liferay.

Alberto

Guevonaso dijo...

Gracias chavalote, me alegro un montón. Espero ser capaz de seguir informandote periodicamente de mis descubrimientos.

Anónimo dijo...

hola!
yo estaba buscando inf. sobre el equals por que me encontre con un caso que no me puedo explicar, y ya sabía que el == comprara referencias y el equals compara que sean los dos objetos del mismo tipo y contengan los mismos valores, pero este código siempre me da falso en los dos if.

Test t1 = new Test();
Test t2 = new Test();
t1.name = "ruly";
t2.name = "ruly";
System.out.println(t1 == t2);
System.out.println(t1.equals(t2));
if (t1 == t2) System.out.println("==");
if (t1.equals(t1)) System.out.println("do =");

alguien sabe por qué???????

Guevonaso dijo...

Para la primera comprobacio (t1==t2) siempre te dará false porque son dos instancias diferentes de la misma clase (Test).

Para la segunda, depende de la implementacion del metodo equals. Por defecto, el metodo equals solo hace la comprobacion de instancia (this == x).
En tu caso el metodo equals deberia de ser algo como :

if (this==x) { return true; }
if (x instanceof Test) {
// falta una verificacion de null,
// para evitar "NullPointerException
// pero la obviamos.
return this.name.equals(((Test)x).name));
}

Si no haces algo como esto, jamás te funcionará lo que estas haciendo

Anónimo dijo...

NO me he enterado de nada, pero añado el link a favoritos para echar un vistazo y comprobar si puedo aprender algo.
Aun así, gracias por compartir

Anónimo dijo...

Hola Guevonaso, soy nuevo en el mundo java, se algo de programacion pero no lo suficiente para solucionar un problemita que tengo con un formulario de correo, no se a quien recurrir por eso es que te molesto.
Mi tema es el siguiente:
Tengo un formuario de correo en mi web y la mayoria de las veces los visitantes escriben mal la direccion de mail, por lo que cuando quiero contactarme con ellos me manda el famoso mensaje de falla ya que la direccion no existe.
lo que hice fue colocar un "repetir mail" pero no se como hacer para que compare el primer textfield con el segundo y que encaso que sean diferentes que centre el foco en el primero para que vuelva a escribirlas. la pagina es www.victoria-colina1.com.ar por si quieres darle un vistazo...
Desde ya muchas gracias y disculpa las molestias.

Martin.
mi mail es martinuga@hotmail.com por si quieres comunicarte.

Anónimo dijo...

gracias, tenía una dudilla, po ya la he resuelto, viendo tu código.

no llores más superman!!!

Sir Andy dijo...

De qué manera equals podría implementarse con un compareTode Comparable o un compare de Comparator, para ordenar una colección de objetos utilizando varios campos?

Guevonaso dijo...

Bueno, más que usar equals para hacer un compareTo implementaría el compareTo y lo usaria para equals. Puesto que compareTo devuelve un 'int' para indicar si es mayor, menor o igual y equals solo devuelve si es igual o no.

Anónimo dijo...

Muy buen ejemplo y muy clara la explicación. Muchas gracias. También probé a implementarlo pasandole como parámetro una Coordenada y aunque al principio funcionaba, me di cuenta de que luego si uso polimorfismo no funcionaba bien... Muchas gracias de nuevo.

Anónimo dijo...

buen dia, todavia tengo duda no comprendo muy bien, me podrias realizar un pequeño codigo para verlo corren.
al mail sombra07@hotmail.com