2012-06-06 12 views
22

que a menudo usan Apache HashCodeBuilder y EqualsBuilder para la igualdad de objetos utilizando la reflexión, pero hace poco un colega me dijeron que el uso de la reflexión puede causar un gran impacto en el rendimiento si la entidad contiene una gran cantidad de propiedades. Preocupado de que pueda estar utilizando una implementación incorrecta, mi pregunta es, ¿cuál de los siguientes enfoques preferiría? ¿Y por qué?HashCodeBuilder y el tipo de uso EqualsBuilder

public class Admin { 

    private Long id; 
    private String userName; 

    public String getUserName() { 
     return userName; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (!(o instanceof Admin)) { 
      return false; 
     } 
     Admin otherAdmin = (Admin) o; 
     EqualsBuilder builder = new EqualsBuilder(); 
     builder.append(getUserName(), otherAdmin.getUserName()); 
     return builder.isEquals(); 
    } 

    @Override 
    public int hashCode() { 
     HashCodeBuilder builder = new HashCodeBuilder(); 
     builder.append(getUserName()); 
     return builder.hashCode(); 
    } 
} 

Vs.

public class Admin { 

    private Long id; 
    private String userName; 

    public String getUserName() { 
     return userName; 
    } 

    @Override 
    public boolean equals(Object o) { 
     return EqualsBuilder.reflectionEquals(this, o, Arrays.asList(id)); 
    } 

    @Override 
    public int hashCode() { 
     return HashCodeBuilder.reflectionHashCode(this, Arrays.asList(id)); 
    } 
} 
+0

tiene que aceptar una de las respuestas debajo de las suites para su pregunta. Yo también tengo la misma pregunta y no puedo justificar cuál es el rito de las respuestas a continuación. Intenta aceptar cualquiera de las respuestas. – developer

+2

¿Por qué tiene que aceptar una de las respuestas a continuación? Si la pregunta no se responde a la satisfacción de la operación, ¿por qué aceptarla? Yo diría que lo dejes abierto hasta que alguien dé una gran respuesta. – Churk

Respuesta

9

Por supuesto, la segunda opción es más elegante y simple. Pero si le preocupa el rendimiento, debe elegir primero, el segundo método también falla si se está ejecutando un administrador de seguridad.

voy a entrar por la primera opción si estuviera en su situación. También hay un error en su primera aproximación en la generación de código hash debe ser de vuelta de builder.toHashCode(); en lugar de builder.hashCode retorno(); (que devuelve código hash constructor de objetos código hash)

+1

Puedo usar hashCode() en lugar de toHashCode(), según la implementación actualizada de HashCodeBuilder, el método hashCode() ahora llama aHashCode() http://commons.apache.org/lang/api-2.6/index.html? org/apache/commons/lang/builder/HashCodeBuilder.html – tintin

+2

Dato curioso: EqualsBuilder.equals() no llama a EqualsBuilder.isEquals(). ¿No es una mala trampa? : -/ –

9

yo preferiría la segunda opción por 2 razones:

  1. Obviamente, es más fácil de leer

  2. No compraría argumentos de rendimiento para la primera opción, a menos que incluyan una métrica relevante. P. ej. ¿Cuántos milisegundos agregará "iguales" basados ​​en la reflexión a una latencia típica de solicitud de extremo a extremo? En general, ¿qué porcentaje de aumento sería ese? Sin saber que las buenas posibilidades son que la optimización es prematuro

3

Su pregunta como está escrito claramente ilustra uno de los beneficios del segundo enfoque:

En el primer caso, se muy fácil de hacer el error return builder.hashCode(), en lugar de la correcta return builder.toHashCode(), que dará lugar a errores sutiles que pueden ser muy difíciles de rastrear.

El segundo caso se elimina la posibilidad de que este error tipográfico, lo que resulta en menos golpearse la cabeza en el teclado tratando de encontrar el fallo.

+0

Este comentario es incorrecto para Apache Commons v2.5 o posterior, Apache Commons v2.5 fue lanzado en febrero de 2010. Para citar el javadoc: "El hashCode calculado de toHashCode() se devuelve debido a la probabilidad de errores en llamadas erróneas a HashCode() y la improbabilidad de que sea lo que es el hashCode para HashCodeBuilder sí mismo ". El javadoc está disponible aquí: http://commons.apache.org/proper/commons-lang/release-history.html – cfogelberg

+0

Es bueno saber que el error ha sido reparado. Sin embargo, la respuesta sigue siendo relevante para personas atrapadas usando la versión anterior de la biblioteca (sí, tiene varios años y la gente debería actualizar, pero hay muchos lugares "empresariales" lentos para avanzar en ese tipo de cosas) – Krease

2

Yo diría que ninguno de estos son una buena implementación. Yo diría que EqualsBuilder no es un buen marco para usar por las siguientes razones:

  1. no extensible. ¿Qué pasa si uno de los campos que intentas afirmar igualdad debe tratar nulos y en blanco como iguales?
  2. debe mantener la lista de variables como si fueran las variables codificadas. Lo que significa que debe enumerar todas las variables que desea comparar. En este punto, no hay ninguna diferencia entre a == o.getA() & & b == o.getB() ...
  3. El uso de la reflexión toma recursos adicionales como ha señalado, y en una empresa aplicación que aplasta miles de millones de objetos. Hacer esta reflexión es tan malo como tener una fuga de memoria.

Voy a decir que es necesario que haya un mejor marco que el Apache.

+0

1. Asegúrese de que las propiedades sean NULL o cadenas vacías (en setters, por ejemplo). Problema resuelto. –

10

Aunque la segunda opción es más atractiva (porque es solo una línea de código) elegiría la primera opción.

La razón es simplemente el rendimiento. Después de realizar una pequeña prueba, encontré una gran diferencia de tiempo entre ellos.

Para suerte de tener una idea del tiempo, he creado este dos clases simples:

package equalsbuildertest; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

public class Class1 { 

    private int field1; 

    private boolean field2; 

    private BigDecimal field3; 

    private String field4; 

    private Date field5; 

    private long field6; 

    public Class1(int field1, boolean field2, BigDecimal field3, String field4, 
      Date field5, long field6) { 
     super(); 
     this.field1 = field1; 
     this.field2 = field2; 
     this.field3 = field3; 
     this.field4 = field4; 
     this.field5 = field5; 
     this.field6 = field6; 
    } 

    public Class1() { 
     super(); 
    } 

    public int getField1() { 
     return field1; 
    } 

    public void setField1(int field1) { 
     this.field1 = field1; 
    } 

    public boolean isField2() { 
     return field2; 
    } 

    public void setField2(boolean field2) { 
     this.field2 = field2; 
    } 

    public BigDecimal getField3() { 
     return field3; 
    } 

    public void setField3(BigDecimal field3) { 
     this.field3 = field3; 
    } 

    public String getField4() { 
     return field4; 
    } 

    public void setField4(String field4) { 
     this.field4 = field4; 
    } 

    public Date getField5() { 
     return field5; 
    } 

    public void setField5(Date field5) { 
     this.field5 = field5; 
    } 

    public long getField6() { 
     return field6; 
    } 

    public void setField6(long field6) { 
     this.field6 = field6; 
    } 

    @Override 
    public boolean equals(Object o) { 
     return EqualsBuilder.reflectionEquals(this, o); 
    } 

    @Override 
    public int hashCode() { 
     return HashCodeBuilder.reflectionHashCode(this); 
    } 

} 

Y:

package equalsbuildertest; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

public class Class2 { 

    private int field1; 

    private boolean field2; 

    private BigDecimal field3; 

    private String field4; 

    private Date field5; 

    private long field6; 

    public Class2(int field1, boolean field2, BigDecimal field3, String field4, 
      Date field5, long field6) { 
     super(); 
     this.field1 = field1; 
     this.field2 = field2; 
     this.field3 = field3; 
     this.field4 = field4; 
     this.field5 = field5; 
     this.field6 = field6; 
    } 

    public Class2() { 
     super(); 
    } 

    public int getField1() { 
     return field1; 
    } 

    public void setField1(int field1) { 
     this.field1 = field1; 
    } 

    public boolean isField2() { 
     return field2; 
    } 

    public void setField2(boolean field2) { 
     this.field2 = field2; 
    } 

    public BigDecimal getField3() { 
     return field3; 
    } 

    public void setField3(BigDecimal field3) { 
     this.field3 = field3; 
    } 

    public String getField4() { 
     return field4; 
    } 

    public void setField4(String field4) { 
     this.field4 = field4; 
    } 

    public Date getField5() { 
     return field5; 
    } 

    public void setField5(Date field5) { 
     this.field5 = field5; 
    } 

    public long getField6() { 
     return field6; 
    } 

    public void setField6(long field6) { 
     this.field6 = field6; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (!(obj instanceof Class2)) { 
      return false; 
     } 
     Class2 other = (Class2) obj; 
     EqualsBuilder builder = new EqualsBuilder(); 
     builder.append(field1, other.field1); 
     builder.append(field2, other.field2); 
     builder.append(field3, other.field3); 
     builder.append(field4, other.field4); 
     builder.append(field5, other.field5); 
     builder.append(field6, other.field6); 
     return builder.isEquals(); 
    } 

    @Override 
    public int hashCode() { 
     HashCodeBuilder builder = new HashCodeBuilder(); 
     builder.append(getField1()); 
     builder.append(isField2()); 
     builder.append(getField3()); 
     builder.append(getField4()); 
     builder.append(getField5()); 
     builder.append(getField6()); 
     return builder.hashCode(); 

    }; 

} 

Esa segunda clase es más o menos el mismo que el primero , pero con diferentes iguales y hashCode.

Después de eso, he creado las siguientes pruebas:

package equalsbuildertest; 

import static org.junit.Assert.*; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.junit.Test; 

public class EqualsBuilderTest { 

    @Test 
    public void test1() { 
     Class1 class1a = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     Class1 class1b = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     for (int i = 0; i < 1000000; i++) { 
      assertEquals(class1a, class1b); 
     } 
    } 

    @Test 
    public void test2() { 
     Class2 class2a = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     Class2 class2b = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     for (int i = 0; i < 1000000; i++) { 
      assertEquals(class2a, class2b); 
     } 
    } 

} 

la prueba son bastante simple y sólo sirve para medir el tiempo.

Los resultados fueron los siguientes:

  • test1 (2,024 s)
  • test2 (0,039 s)

les escogió para ser completamente iguales a fin de tener los mejores tiempos Si elige realizar la prueba con las condiciones de NotEquals tendrá menos tiempo, pero también una gran diferencia horaria.

Ejecuto estas pruebas en una CPU Intel Core i5-3317U de 64 bits a 1.70 GHz x4 con Fedora 21 y Eclipse Luna.

En conclusión, no me arriesgaría a tener una gran diferencia de rendimiento para guardar un par de líneas de código que posiblemente no pueda escribir con una plantilla (en Eclipse en Windows -> Preferencias en Java -> editor -> Plantillas) como este:

${:import(org.apache.commons.lang3.builder.HashCodeBuilder, org.apache.commons.lang3.builder.EqualsBuilder)} 
@Override 
public int hashCode() { 
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); 
    hashCodeBuilder.append(${field1:field}); 
    hashCodeBuilder.append(${field2:field}); 
    hashCodeBuilder.append(${field3:field}); 
    hashCodeBuilder.append(${field4:field}); 
    hashCodeBuilder.append(${field5:field}); 
    return hashCodeBuilder.toHashCode(); 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj == null) { 
     return false; 
    } 
    if (getClass() != obj.getClass()) { 
     return false; 
    } 
    ${enclosing_type} rhs = (${enclosing_type}) obj; 
    EqualsBuilder equalsBuilder = new EqualsBuilder(); 
    equalsBuilder.append(${field1}, rhs.${field1}); 
    equalsBuilder.append(${field2}, rhs.${field2}); 
    equalsBuilder.append(${field3}, rhs.${field3}); 
    equalsBuilder.append(${field4}, rhs.${field4}); 
    equalsBuilder.append(${field5}, rhs.${field5});${cursor} 
    return equalsBuilder.isEquals(); 
} 
1

de acuerdo con @Churk, Apache HashCodeBuilder y EqualsBuilder no se aplican bien. ¡HashCodeBuilder todavía está jugando con números primos! Además, hace muchísimo trabajo innecesario. ¿Has leído la fuente?

Desde Java 5 (si no antes), AbstractHashMap <> no ha utilizado el módulo de un número primo para localizar el cubo hash. En cambio, la cantidad de cubos es una potencia de dos y los N bits de orden baja del código hash se usan para ubicar el cucharón.

Además, "mezclará" el código hash proporcionado por la aplicación para que los bits se distribuyan de manera uniforme y, por lo tanto, los depósitos se llenan uniformemente.

Por lo tanto, la forma correcta para anular int Object.hashCode() es devolviendo el valor más simple, constante con la más alta aridad en la población de objetos que se cohabitar en cualquier colección utilizando la clase.

Normalmente, el valor de ID sin modificaciones es su mejor opción. Si su campo ID es integral, solo empújelo a (int) y devuélvalo.Si es una Cadena u otro Objeto, solo devuelve su código hash. Entiendes la idea. Para un identificador compuesto, devuelva el campo (o hashCode del mismo) con los valores más distintos. Menos es más.

Por supuesto, el contrato entre hashCode() y equals() se debe cumplir. Por lo tanto, equals() debe implementarse en consecuencia. hashCode() no necesita usar los calificadores completos necesarios para la igualdad, pero cualquier campo usado en hashCode() debe usarse en equals(). Aquí, los métodos como StringUtils.equals (s1, s2) son útiles para manejar los valores nulos de forma consistente y segura.

+0

Aún más importante: HashCodeBuilder.reflectionHashCode() y EqualsBuilder.reflectionEquals() son en realidad algo peligrosos ya que pueden violar fácilmente la parte "consistente" del contrato equals()/hashCode(). Esto es lo que sucede: si un objeto se coloca en una tabla/conjunto hash de algún tipo y, posteriormente, se cambia un campo usado en hashCode() o igual(), el objeto queda huérfano en la colección y nunca se puede acceder . Solo los campos invariantes e identificadores se deben usar en hashCode() o equals(). El desarrollador no puede dejar esta elección a un "generador" genérico. – Charlie

+0

cambiar campos después de insertar objetos en una colección es un problema para cualquier implementación equals/hash – Bax

Cuestiones relacionadas