Estos 3 métodos están incluidos en la clase Object y por tanto presentes en todos los objetos Java ya que en Java todos los objetos heredan de Object. ¿Para que sirven estos métodos? ¿Cuál es su importancia?
El método hashCode se usa para definir la función de
dispersión en las tablas dispersas (tablas hash). Una tabla hash utiliza asocia
elementos <clave, valor> en la cual los elementos de la clave no tienen
orden sino que la posición viene determinada por está función, de manera que
según sea de buena la función de dispersión las operaciones sobre la tabla
serán más o menos eficientes.
El método equals se usa para comparar 2 instancias de
una misma clase y comprobar si son iguales o no. Igual que en hashCode la importancia de una buena
implementación del método tendrá impacto sobre el rendimiento al realizar
operaciones que requieran su uso como la búsqueda en arrays, la comparación en
listas, etc.
El método toString se usa para mostrar una representación
del objeto. Este método no es tan importante como los dos anteriores pero
igualmente es muy recomendable una buena implementación para cuando se use
tenga un buen rendimiento.
El contrato de hashCode() debe cumplir que para una instancia de un objeto devuelva
el mismo valor cada vez que se invoca y que sea consistente con el método equals(), si dos objetos son
iguales deben devolver el mismo hashCode.
La función equals() debe cumplir las propiedades
reflexiva, simétrica y transitiva.
1: import java.util.Date;
2:
3:
4: /**
5: * @author Alvaro
6: *
7: */
8: public class Person
9: {
10: private Long id;
11: private String name;
12: private String surname;
13: private String address;
14: private Date birthDate;
15:
16: /**
17: * @return the id
18: */
19: public Long getId()
20: {
21: return id;
22: }
23:
24: /**
25: * @param id the id to set
26: */
27: public void setId(Long id)
28: {
29: this.id = id;
30: }
31:
32: /**
33: * @return the name
34: */
35: public String getName()
36: {
37: return name;
38: }
39:
40: /**
41: * @param name the name to set
42: */
43: public void setName(String name)
44: {
45: this.name = name;
46: }
47:
48: /**
49: * @return the surname
50: */
51: public String getSurname()
52: {
53: return surname;
54: }
55:
56: /**
57: * @param surname the surname to set
58: */
59: public void setSurname(String surname)
60: {
61: this.surname = surname;
62: }
63:
64: /**
65: * @return the address
66: */
67: public String getAddress()
68: {
69: return address;
70: }
71:
72: /**
73: * @param address the address to set
74: */
75: public void setAddress(String address)
76: {
77: this.address = address;
78: }
79:
80: /**
81: * @return the birthDate
82: */
83: public Date getBirthDate()
84: {
85: return birthDate;
86: }
87:
88: /**
89: * @param birthDate the birthDate to set
90: */
91: public void setBirthDate(Date birthDate)
92: {
93: this.birthDate = birthDate;
94: }
95: }
Si trabajamos con un entorno de
desarrollo como Eclipse o Netbeans tenemos
herramientas que nos facilitan la manera de implementarlo automáticamente,
únicamente indicando con que propiedades debe implementarse cada una de los
métodos. En Eclipse podemos
hacerlo con ALT + SHIFT + S y
seleccionando Generate
hashCode() and equals()... o Generate
toString()...
Sólo tenemos que seleccionar manualmente los atributos que vamos a utilizar para definir nuestro método.
Aunque esté método genera un código que cumple las condiciones del contrato
puede generar mucho código si el objeto tiene muchos atributos.
1: /* (non-Javadoc)
2: * @see java.lang.Object#hashCode()
3: */
4: @Override
5: public int hashCode()
6: {
7: final int prime = 31;
8: int result = 1;
9: result = (prime * result) + ((address == null) ? 0 : address.hashCode());
10: result = (prime * result) + ((birthDate == null) ? 0 : birthDate.hashCode());
11: result = (prime * result) + ((id == null) ? 0 : id.hashCode());
12: result = (prime * result) + ((name == null) ? 0 : name.hashCode());
13: result = (prime * result) + ((surname == null) ? 0 : surname.hashCode());
14:
15: return result;
16: }
17:
18: /* (non-Javadoc)
19: * @see java.lang.Object#equals(java.lang.Object)
20: */
21: @Override
22: public boolean equals(Object obj)
23: {
24: if (this == obj)
25: {
26: return true;
27: }
28:
29: if (obj == null)
30: {
31: return false;
32: }
33:
34: if (getClass() != obj.getClass())
35: {
36: return false;
37: }
38:
39: Person other = (Person) obj;
40:
41: if (address == null)
42: {
43: if (other.address != null)
44: {
45: return false;
46: }
47: }
48: else if (!address.equals(other.address))
49: {
50: return false;
51: }
52:
53: if (birthDate == null)
54: {
55: if (other.birthDate != null)
56: {
57: return false;
58: }
59: }
60: else if (!birthDate.equals(other.birthDate))
61: {
62: return false;
63: }
64:
65: if (id == null)
66: {
67: if (other.id != null)
68: {
69: return false;
70: }
71: }
72: else if (!id.equals(other.id))
73: {
74: return false;
75: }
76:
77: if (name == null)
78: {
79: if (other.name != null)
80: {
81: return false;
82: }
83: }
84: else if (!name.equals(other.name))
85: {
86: return false;
87: }
88:
89: if (surname == null)
90: {
91: if (other.surname != null)
92: {
93: return false;
94: }
95: }
96: else if (!surname.equals(other.surname))
97: {
98: return false;
99: }
100:
101: return true;
102: }
¿Por qué se suelen usar números primos para generar el hashCode?
Porque es una buena estrategia
para que no produzcan muchas colisiones cuando la tabla tenga
muchos elementos. Como los números
primos no son divisibles por otros números, si multiplicamos los
diferentes atributos que se usan en la función por números primos, es mucho más
improbable que diferentes objetos generen el mismo valor.
Si trabajamos en un contexto de base de datos y estamos utilizando un framework ORM (como Hibernate o Ibatis),
y tenemos pojos para mapear tablas podemos tener en cuenta el hecho de tener primary keys. El valor de una primary
key es único para cada
elemento. Si tenemos un pojo con un atributo que representa la primary key, este atributo será
suficiente para implementar hashCode() e equals() al
ser un valor único y comparable.
En el ejemplo se implementaría manualmente así:
1: @Override
2: public int hashCode()
3: {
4: return id.hashCode();
5: }
6:
7: @Override
8: public boolean equals(Object obj)
9: {
10: boolean result = false;
11:
12: if ((obj != null) && obj instanceof Person)
13: {
14: Person pojo = (Person) obj;
15: result = (id != null) && id.equals(pojo.getId());
16: }
17:
18: return result;
19: }
Existen algunas librerías que ayudan a sobrescribir las tres funciones de
manera eficiente y minimizando el tamaño del código. Una de las más conocidas
es Apache Commons Lang (http://commons.apache.org/lang) que posee entre otras utilidades HashCodeBuilder, EqualsBuilder yToStringBuilder.
Imaginemos que en el ejemplo anterior
que deseamos utilizar sólo el id correspondiente a la PK usando estas
librerías. Además daremos un paso más allá encapsulando esta funcionalidad en
clase abstracta usando un tipo genérico para poder usarla en todos los pojos
que tengamos y tener así definido los métodos para todos. De esta manera el id
podrá ser cualquier tipo primitivo (String, Integer, Long, etc.) o incluso un
id compuesto, es decir, otro bean.
1: /**
2: * @author Alvaro
3: *
4: * @param <T>
5: */
6: public abstract class AbstractGenericModel<T extends Comparable<T>>
7: {
8: private T id;
9:
10: /**
11: * @return the id
12: */
13: @Id
14: @Column(name = "ID")
15: public T getId()
16: {
17: return id;
18: }
19:
20: /**
21: * @param id the id to set
22: */
23: public void setId(T id)
24: {
25: this.id = id;
26: }
27:
28: /**
29: *{@inheritDoc}
30: */
31: @Override
32: public int hashCode()
33: {
34: return new HashCodeBuilder().append(id).toHashCode();
35: }
36:
37: /**
38: *{@inheritDoc}
39: */
40: @SuppressWarnings("unchecked")
41: @Override
42: public boolean equals(Object obj)
43: {
44: boolean result = false;
45:
46: if ((obj != null) && obj instanceof Person)
47: {
48: AbstractGenericModel<T> pojo = (AbstractGenericModel<T>) obj;
49: new EqualsBuilder().append(id, pojo.getId());
50: }
51:
52: return result;
53: }
54: }
Si el nombre del id en base de datos es diferente de "ID" podemos
sobre-escribirlo desde la clase hija. Por tanto si implementamos la clase inicial Person que suponemos que tenemos
mapeada a una tabla PERSONS usando está implementación genérica quedaría así:
1:
2: import org.apache.commons.lang.builder.EqualsBuilder;
3:
4: import java.util.Date;
5:
6: import javax.persistence.Column;
7: import javax.persistence.Entity;
8: import javax.persistence.Table;
9:
10:
11: /**
12: * @author Alvaro
13: *
14: */
15: @Entity
16: @Table(name = "PERSONS", schema = "TEST")
17: public class Person extends AbstractGenericModel<Long>
18: {
19: private String name;
20: private String surname;
21: private String address;
22: private Date birthDate;
23:
24: /**
25: * @return the name
26: */
27: @Column(name = "NAME")
28: public String getName()
29: {
30: return name;
31: }
32:
33: /**
34: * @param name the name to set
35: */
36: public void setName(String name)
37: {
38: this.name = name;
39: }
40:
41: /**
42: * @return the surname
43: */
44: @Column(name = "SURNAME")
45: public String getSurname()
46: {
47: return surname;
48: }
49:
50: /**
51: * @param surname the surname to set
52: */
53: public void setSurname(String surname)
54: {
55: this.surname = surname;
56: }
57:
58: /**
59: * @return the address
60: */
61: @Column(name = "ADDRESS")
62: public String getAddress()
63: {
64: return address;
65: }
66:
67: /**
68: * @param address the address to set
69: */
70: public void setAddress(String address)
71: {
72: this.address = address;
73: }
74:
75: /**
76: * @return the birthDate
77: */
78: @Column(name = "BIRTH_DATE")
79: public Date getBirthDate()
80: {
81: return birthDate;
82: }
83:
84: /**
85: * @param birthDate the birthDate to set
86: */
87: public void setBirthDate(Date birthDate)
88: {
89: this.birthDate = birthDate;
90: }
91:
92: /**
93: * {@inheritDoc}
94: */
95: @Override
96: public String toString()
97: {
98: return new EqualsBuilder().append("id", getId()).append("name", name).append("surname", surname)
99: .append("address", address).append("birthDate", birthDate).toString();
100: }
101: }
Debemos sobrescribir estos 3 métodos cuando sea necesario para nuestra
aplicación de la manera más óptima y escribiendo la menor cantidad de código.
Así cuando se haga uso de los objetos por parte de algoritmos se repercutirá
positivamente en los mismos haciéndolos más eficientes, y nuestro código será
claro y legible por otros.


No comments:
Post a Comment