Apache Commons es igual a / hashCode builder

Tengo curiosidad por saber qué piensa la gente de aquí usando org.apache.commons.lang.builder EqualsBuilder / HashCodeBuilder para implementar equals / hashCode ? ¿Sería una mejor práctica que escribir la tuya? ¿Funciona bien con Hibernate? ¿Cual es tu opinion?

Los constructores commons / lang son geniales y los he usado durante años sin una sobrecarga de rendimiento notable (con y sin hibernación). Pero como escribe Alain, la manera de la guayaba es aún más agradable:

Aquí hay un frijol de muestra:

 public class Bean{ private String name; private int length; private List children; } 

Aquí están equals () y hashCode () implementados con Commons / Lang:

 @Override public int hashCode(){ return new HashCodeBuilder() .append(name) .append(length) .append(children) .toHashCode(); } @Override public boolean equals(final Object obj){ if(obj instanceof Bean){ final Bean other = (Bean) obj; return new EqualsBuilder() .append(name, other.name) .append(length, other.length) .append(children, other.children) .isEquals(); } else{ return false; } } 

y aquí con Java 7 o superior (inspirado en Guava):

 @Override public int hashCode(){ return Objects.hash(name, length, children); } @Override public boolean equals(final Object obj){ if(obj instanceof Bean){ final Bean other = (Bean) obj; return Objects.equals(name, other.name) && length == other.length // special handling for primitives && Objects.equals(children, other.children); } else{ return false; } } 

Nota: este código originalmente hace referencia a Guava, pero como los comentarios han señalado, esta funcionalidad se ha introducido desde entonces en el JDK, por lo que ya no se requiere Guava.

Como puede ver, la versión de Guava / JDK es más corta y evita los objetos auxiliares superfluos. En el caso de iguales, incluso permite el cortocircuito de la evaluación si una llamada Object.equals() anterior devuelve falso (para ser justos: commons / lang tiene un ObjectUtils.equals(obj1, obj2) con semántica idéntica que podría ser utilizado en lugar de EqualsBuilder para permitir el cortocircuito como se EqualsBuilder anteriormente).

Entonces, sí, los constructores commons lang son muy preferibles sobre los métodos equals() y hashCode() manualmente construidos (o esos horribles monstruos que Eclipse generará para ti), pero las versiones Java 7+ / Guava son incluso mejores.

Y una nota sobre Hibernate:

tenga cuidado con el uso de colecciones perezosas en las implementaciones equals (), hashCode () y toString (). Eso fracasará miserablemente si no tienes una sesión abierta.


Nota (sobre igual ()):

a) en ambas versiones de iguales () arriba, es posible que desee utilizar uno o ambos de estos accesos directos también:

 @Override public boolean equals(final Object obj){ if(obj == this) return true; // test for reference equality if(obj == null) return false; // test for null // continue as above 

b) dependiendo de su interpretación del contrato igual (), también puede cambiar la (s) línea (s)

  if(obj instanceof Bean){ 

a

  // make sure you run a null check before this if(obj.getClass() == getClass()){ 

Si usa la segunda versión, probablemente también quiera llamar a super(equals()) dentro de su método equals() . Las opiniones difieren aquí, el tema se discute en esta pregunta:

forma correcta de incorporar la superclase en una implementación Guava Objects.hashcode ()?

(Aunque se trata de hashCode() , lo mismo se aplica a equals() )


Nota (inspirada en los comentarios de kayahr )

Objects.hashCode(..) (igual que el Arrays.hashCode(...) ) subyacente puede funcionar mal si tiene muchos campos primitivos. En tales casos, EqualsBuilder puede ser la mejor solución.

Gente, despierta! Desde Java 7 existen métodos auxiliares para equals y hashCode en la biblioteca estándar. Su uso es totalmente equivalente al uso de métodos de guayaba.

Si no quiere depender de una biblioteca de terceros (tal vez está ejecutando un dispositivo con recursos limitados) e incluso no desea escribir sus propios métodos, también puede dejar que el IDE haga el trabajo, por ejemplo, en el uso del eclipse.

 Source -> Generate hashCode() and equals()... 

Obtendrá un código ‘nativo’ que puede configurar a su gusto y que debe admitir en los cambios.


Ejemplo (eclipse Juno):

 import java.util.Arrays; import java.util.List; public class FooBar { public String string; public List stringList; public String[] stringArray; /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((string == null) ? 0 : string.hashCode()); result = prime * result + Arrays.hashCode(stringArray); result = prime * result + ((stringList == null) ? 0 : stringList.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FooBar other = (FooBar) obj; if (string == null) { if (other.string != null) return false; } else if (!string.equals(other.string)) return false; if (!Arrays.equals(stringArray, other.stringArray)) return false; if (stringList == null) { if (other.stringList != null) return false; } else if (!stringList.equals(other.stringList)) return false; return true; } } 

EqualsBuilder y HashCodeBuilder tienen dos aspectos principales que son diferentes del código escrito manualmente:

  • manejo nulo
  • creación de instancia

EqualsBuilder y HashCodeBuilder hacen que sea más fácil comparar campos que podrían ser nulos. Con el código escrito manualmente, esto crea una gran cantidad de texto repetitivo.

EqualsBuilder, por otro lado, creará una instancia por llamada de método igual. Si sus métodos iguales son llamados a menudo, esto creará muchas instancias.

Para Hibernate, la implementación de igual y hashCode no hace ninguna diferencia. Son solo un detalle de implementación. Para casi todos los objetos de dominio cargados con hibernación, se puede ignorar la sobrecarga de tiempo de ejecución (incluso sin análisis de escape) del generador . La sobrecarga de la base de datos y las comunicaciones será significativa.

Como mencionó skaffman, la versión de reflexión no se puede usar en el código de producción. La reflexión será reducir la velocidad y la “implementación” no será correcta para todas las clases, excepto para las más simples. Tomar en cuenta a todos los miembros también es peligroso ya que los miembros recién introducidos cambian el comportamiento del método igual. La versión de reflexión puede ser útil en el código de prueba.

Si no escribe el suyo, también existe la posibilidad de utilizar google guava (anteriormente google collections)

Si solo está tratando con entity bean donde id es una clave principal, puede simplificar.

  @Override public boolean equals(Object other) { if (this == other) { return true; } if ((other == null) || (other.getClass() != this.getClass())) { return false; } EntityBean castOther = (EntityBean) other; return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals(); } 

En mi opinión, no funciona bien con Hibernate, especialmente los ejemplos de la respuesta que compara longitud, nombre e hijos para alguna entidad. Hibernate recomienda usar la clave comercial para ser utilizada en equals () y hashCode (), y tienen sus razones. Si usa el generador auto equals () y hashCode () en su clave comercial, está bien, solo los problemas de rendimiento deben considerarse como se mencionó anteriormente. Pero la gente generalmente usa todas las propiedades, lo que es muy incorrecto. Por ejemplo, actualmente estoy trabajando en un proyecto donde las entidades se escriben usando Pojomatic con @AutoProperty, lo que considero un patrón realmente malo.

Sus dos escenarios principales para usar hashCode () y equals () son:

  • cuando coloca instancias de clases persistentes en un conjunto (la forma recomendada de representar asociaciones de muchos valores) y
  • cuando usa la readaptación de instancias separadas

Entonces supongamos que nuestra entidad se ve así:

 class Entity { protected Long id; protected String someProp; public Entity(Long id, String someProp); } Entity entity1 = new Entity(1, "a"); Entity entity2 = new Entity(1, "b"); 

Ambas son la misma entidad para Hibernate, que han sido extraídas de alguna sesión en algún momento (su id y class / table son iguales). Pero cuando implementamos auto equals () un hashCode () en todos los accesorios, ¿qué tenemos?

  1. Cuando coloca la entidad2 en el conjunto persistente donde ya existe la entidad1, se colocará dos veces y dará lugar a una excepción durante la confirmación.
  2. Si desea adjuntar la entidad separada2 a la sesión, donde ya existe la entidad1, (probablemente, no lo he probado especialmente) no se fusionarán correctamente.

Entonces, para el 99% del proyecto que realizo, usamos la siguiente implementación de equals () y hashCode () escritos una vez en la clase de entidad base, lo cual es consistente con los conceptos de Hibernate:

 @Override public boolean equals(Object obj) { if (StringUtils.isEmpty(id)) return super.equals(obj); return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId()); } @Override public int hashCode() { return StringUtils.isEmpty(id) ? super.hashCode() : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode(); } 

Para la entidad transitoria, hago lo mismo que Hibernate en el paso de persistencia, es decir. Yo uso la coincidencia de instancia. Para los objetos persistentes comparo la clave única, que es la tabla / id (nunca uso claves compuestas).

Por si acaso, a otros les resultará útil, he creado esta clase de Ayuda para el cómputo del código hash que evita la sobrecarga de creación de objetos adicionales mencionados anteriormente (de hecho, la sobrecarga del método Objects.hash () es aún mayor cuando tienes herencia, ya que creará una nueva matriz en cada nivel!).

Ejemplo de uso:

 public int hashCode() { return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long } public int hashCode() { return HashCode.hash(super.hashCode(), occupation, children); } 

El ayudante HashCode:

 public class HashCode { public static int hash(Object o1, Object o2) { return add(Objects.hashCode(o1), o2); } public static int hash(Object o1, Object o2, Object o3) { return hash(Objects.hashCode(o1), o2, o3); } ... public static int hash(Object o1, Object o2, ..., Object o10) { return hash(Objects.hashCode(o1), o2, o3, ..., o10); } public static int hash(int initial, Object o1, Object o2) { return add(add(initial, o1), o2); } ... public static int hash(int initial, Object o1, Object o2, ... Object o10) { return add(... add(add(add(initial, o1), o2), o3) ..., o10); } public static int hash(long value) { return (int) (value ^ (value >>> 32)); } public static int hash(int initial, long value) { return add(initial, hash(value)); } private static int add(int accumulator, Object o) { return 31 * accumulator + Objects.hashCode(o); } } 

He calculado que 10 es la cantidad máxima razonable de propiedades en un modelo de dominio. Si tiene más, debería pensar en refactorizar e introducir más clases en lugar de mantener un montón de cadenas y primitivas.

Los inconvenientes son: no es útil si tiene principalmente primitivas y / o matrices que necesita hash profundamente. (Normalmente este es el caso cuando tiene que ocuparse de objetos planos (de transferencia) que están fuera de su control).