Herramientas de usuario

Herramientas del sitio


unidades:03_relaciones:07_equals

Diferencias

Muestra las diferencias entre dos versiones de la página.


unidades:03_relaciones:07_equals [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== Equals ======
 +El método <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc> es de gran importancia al usar las colecciones en Java. En la mayoría de las clases de colecciones Java es importante decidir si dos objetos son iguales y para ello se usa el método <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc>. En Hibernate tiene aun mas importancia debido a que realmente dos objetos son iguales si hacen referencia a las misma fila de la base de datos aunque los objetos sean distintos. En este tema veremos como implementar correctamente el método  <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc> y el método <javadoc jdk7>java.lang.Object#hashCode()|hashCode()</javadoc>, ya que éste último está íntimamente relacionado con <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc>.
  
 +
 +<note tip>
 +La explicación completa sobre los métodos <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc> y <javadoc jdk7>java.lang.Object#hashCode()|hashCode()</javadoc> está mas allá del ámbito de este curso, ya que es un tema exclusivamente de Java. Por suerte en internet hay un a extensa documentación sobre éste tema , incluyendo documentación en castellano. Os dejos unos enlaces sobre ello:
 +  * [[http://code.google.com/p/java-best-practices-course/wiki/equalsHashcode|Métodos equals y hashCode]]
 +  * [[http://www.dosideas.com/noticias/java/758-java-hashing.html|Java Hashing]]
 +  * [[http://anabuigues.com/2010/07/06/como-sobreescribir-los-metodos-equals-y-hashcode-de-java/|Cómo sobreescribir los métodos equals y hashCode de Java]]
 +</note> 
 +
 +===== Datos a comparar=====
 +Como ya hemos comentado , al implementar los dos métodos debemos tener en cuenta que para Hibernate dos objetos serán iguales si hacen referencia a la misma fila de la base de datos. Esto va a hacer que no haya una solución fácil para implementar ambos métodos. Veamos algunas soluciones y sus problemas.
 +
 +==== Claves primarias ====
 +Una solución fácil sería comparan las claves primarias de los 2 objetos. 
 +
 +En la siguiente unidad está el tema dedicado a las [[unidades:04_claves_primarias_y_tipos_datos:02_claves_primarias]], ahí veremos como hacer que se generen automáticamente pero con el problema de que se hace cuando se va a guardar el objeto, así que la solución de comparar las claves primarias no es buena ya que para nuevos objetos no podríamos compararlos aunque realmente hicieran referencia a la misma fila.
 + 
 +==== Claves primarias naturales ====
 +En el tema [[unidades:04_claves_primarias_y_tipos_datos:02_claves_primarias]] también hablaremos de las claves primarias naturales, también llamadas claves de negocio, como son el DNI, el login o el [[wpes>Código_cuenta_cliente|Código Cuenta Cliente]]. Aunque estas claves no son adecuadas para ser claves primarias si que nos resultarán útiles para ser usadas en los métodos <javadoc jdk7>java.lang.Object#equals(java.lang.Object)|equals(Object)</javadoc> y <javadoc jdk7>java.lang.Object#hashCode()|hashCode()</javadoc>.
 +
 +Las claves primarias naturales ,tienen la caracteristica de que hacen referencia a una única fila de la base de datos y por regla general se sabe su valor antes de crear la fila en la base de datos por lo que dos objetos que hagan referencia a los mismos datos nunca serán iguales.
 +
 +¿Cual es el problema de las claves primarias naturales? Que quizás la entidad de Hibernate no posea una clave primaria natural con lo que esta técnica no funcionará, aun así es la mejor opción que tenemos (( Realmente hay más opciones con soluciones mas complejas y barrocas pero no vamos a entrar en ello. ))
 +==== No hacer nada ====
 +Una última opción es simplemente no hacer nada. Si nuestra aplicación va a tener el cuidado de no añadir el mismo objeto dos veces a una colección o nunca va a hacer un ''equals'' sino que es suficiene con ''=='' , etc, etc, entonces no es necesario hacer nada.
 +
 +===== Usuario =====
 +Para implementar los métodos ''equals'' y ''hashCode'' vamos a usar como ejemplo la clase ''Usuario''. Esta clase tiene el siguiente código Java:
 +
 +<code java 1 | Usuario.java>
 +public class Usuario implements Serializable {
 +
 +    private int idUsuario;
 +    private String login;
 +    private String nombre;
 +    private String ape1;
 +    private String ape2;
 +    private String password;
 +
 +    public Usuario() {
 +    }
 +
 +    public Usuario(String login, String nombre, String ape1, String ape2, String password) {
 +        this.login = login;
 +        this.nombre = nombre;
 +        this.ape1 = ape1;
 +        this.ape2 = ape2;
 +        this.password = password;
 +    }
 +
 +    @Override
 +    public String toString() {
 +        return getLogin() + "-" + getNombre() + " " + getApe1() + " " + getApe2();
 +    }
 + }
 +</code>
 +
 +<note important>En la clase Java ''Usuario'' no se han incluido los métodos get/set de cada propiedad para facilitar la lectura, pero deben estar en la clase Java.</note>
 +
 +El diagrama UML es el siguiente:
 +
 +<uml>
 +class Usuario
 +Usuario : int idUsuario
 +Usuario : String login
 +Usuario : String nombre
 +Usuario : String ape1
 +Usuario : String ape2
 +Usuario : String password
 +</uml>
 +
 +La clase ''Usuario'' tiene la característica de que la propiedad ''login'' es una clave primaria natural y tener valor antes de guardarse en la base de datos y cada valor de ''login'' hace referencia una única fila de la base de datos, por lo tanto es una opción correcta para usarlo en los métodos ''equals'' y ''hashCode''.
 +
 +Veamos ahora como se implementan ambos métodos:
 +
 +<code java 1 | Implementación de equals>
 +@Override
 +public boolean equals(Object obj) {
 +    if (this == obj) {
 +        return true;
 +    }
 +    if (obj == null) {
 +        return false;
 +    }
 +    if (Hibernate.getClass(this) != Hibernate.getClass(obj)) {
 +        return false;
 +    }
 +
 +    Usuario usuario = (Usuario) obj;
 +    Object dato1=getLogin() ;
 +    Object dato2=usuario.getLogin();
 +
 +    if (dato1 == null) {
 +        if (dato2==null) {
 +            return true;
 +        } else {
 +            return false;
 +        }
 +    } else if (dato1.equals(dato2) == true) {
 +        return true;
 +    } else {
 +        return false;
 +    }
 +}
 +</code>
 +
 +Pasemos ahora a explicar como funciona el método:
 +  * Líneas 3-5: Si los objetos son exactamente el mismo porque sus //punteros// coinciden, entonces retornamos ''true''.
 +  * Líneas 6-8: Si el otro objeto es ''null'', nunca serán iguales ya que éste no lo es, entonces retornamos ''false''.
 +  * Línea 9-11: Vemos si ambos son del mismo tipo porque si no lo son es que no serán iguales, entonces retornamos ''false''.
 +  * Líneas 13-15: Obtenemos el valor de ambos objetos para la propiedad que es la clave primaria natural ,en nuestro caso la propiedad ''login''.
 +  * Líneas 17-27: Si ambos datos son iguales o ambos son ''null'' los objetos serán iguales y retornamos ''true'' en otro caso es que son distintos y entonces retornamos ''false''.
 +
 +<note tip>
 +En el famoso libro [[http://www.oracle.com/technetwork/articles/javase/bloch-effective-08-qa-140880.html|Effective Java (2nd Edition)]] se trata el tema de como comparar si dos clases son del mismo tipo. Joshua Bloch (( autor del libro, creador del framework de colecciones en Java, etc. )) indica usar la orden ''instanceof'' sin embargo hay otra opción que es usar ''getClass()''. En la siguiente entrevista a Joshua Bloch [[http://www.artima.com/intv/bloch17.html|instanceof versus getClass in equals Methods]] justifica su decisión indicando que una subclase de la clase también podría ser igual si tienen //los mismos valores//.
 +
 +Mi opinión personal es que usando Hibernate al hacer una subclase lo normal es que se añadan campos , por lo tanto estaremos haciendo referencia a mas filas de la base de datos, por ello es preferible usar ''getClass()''. El problema de usar ''getClass()'' es que Hibernate puede usar objetos proxy y el método ''getClass()'' no funcionará. Por suerte Hibernate nos da la solución a este pequeño problema usando el método <javadoc h41>org.hibernate.Hibernate#getClass(java.lang.Object)|Hibernate.getClass(Object obj)</javadoc>.
 +</note>
 +
 +
 +<code java 1 | Implementación de hashCode >
 +@Override
 +public int hashCode() {
 +    Object dato1=getLogin() ;
 +    int resultado;
 +
 +    resultado = 31 * resultado + (dato1 == null ? 0 : dato1.hashCode());
 +
 +    return resultado;
 +}
 +</code>
 +
 +El código de este método es bastante sencillo aunque el porqué de ello es mas complejo y no entraremos a explicarlo sin embargo en el libro [[http://www.oracle.com/technetwork/articles/javase/bloch-effective-08-qa-140880.html|Effective Java (2nd Edition)]] hay una explicación de ello.
 +  * Línea 3: Obtenemos el valor de la clave primaria natural, que en nuestro caso es ''login''.
 +  * Línea 4: Elegimos un número arbitrario que debería ser distinto del de otras clases que diseñemos.
 +  * Línea 6: Multiplicamos el valor anterior por 31 (( Se usa este valor por ser un número primo: [[http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/|Why do hash functions use prime numbers?]] )) y le sumamos el hash de la clave primaria natural.
 +
 +Con toda esta explicación no debería ser difícil crear nuevas funciones ''equals'' y ''hashCode'' para otras clases que se usen en Hibernate.
 +===== Referencias =====
 +  * [[http://www.oracle.com/technetwork/articles/javase/bloch-effective-08-qa-140880.html|Effective Java (2nd Edition)]]
 +  * [[http://wrschneider.blogspot.com.es/2012/01/equals-and-hashcode-on-entity-classes.html|equals and hashCode on @Entity classes: just say no]] 
 +  * [[https://community.jboss.org/wiki/EqualsAndHashCode|Equals and HashCode]]
 +  * [[http://federicovarela.blogspot.com.es/2008/02/equals-y-hashcode-en-hibernate.html|equals y hashCode en Hibernate]]
 +  * [[http://javarevisited.blogspot.sg/2011/02/how-to-write-equals-method-in-java.html|What Every Programmers Should know about Overriding equals() and hashCode() method in Java and Hibernate]]
 +  * [[http://dertompson.com/2010/05/15/equals-and-hashcode-and-hibernate/|Equals and Hashcode and Hibernate]]
 +  * [[http://blog.andrewbeacock.com/2008/08/how-to-implement-hibernate-safe-equals.html| How to implement a Hibernate-safe equals() method using instanceof and accessors (getters) in Eclipse]]
 +  * [[http://arnosoftwaredev.blogspot.com.es/2009/05/implementing-equals-and-hashcode-in.html|Implementing Equals And HashCode In Hibernate POCOs/POJOs]]
 +  * [[http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html|Don't Let Hibernate Steal Your Identity]]
 +  * [[http://www.ideyatech.com/2011/04/effective-java-equals-and-hashcode|Effective Java: Equals and HashCode]]
 +  * [[http://eclipsesource.com/blogs/2012/09/04/the-3-things-you-should-know-about-hashcode/|The 3 things you should know about hashCode()]]
 +  * [[http://anabuigues.com/2010/07/06/como-sobreescribir-los-metodos-equals-y-hashcode-de-java/|Cómo sobreescribir los métodos equals y hashCode de Java]]
 +  * [[http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/|Why do hash functions use prime numbers?]]