Saturday, November 3, 2012

equals(), hashCode(), toString() [ES]

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