A menudo utilizo apache HashCodeBuilder y EqualsBuilder por objeto la igualdad mediante la reflexión, pero recientemente un colega me dijo que el uso de la reflexión puede causar un gran impacto en el rendimiento si la entidad que contiene una gran cantidad de propiedades. Preocupado de que me puede estar usando una equivocada aplicación, mi pregunta es, cual de las siguientes enfoque prefiere? Y ¿por qué?

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));
    }
}
  • es necesario aceptar una de las respuestas más abajo que las suites a tu pregunta. Yo también tengo la misma pregunta y no puede justificar que se rito de abajo respuestas. Trate de aceptar alguna de la respuesta.
  • ¿Por qué él tiene que aceptar una de las respuestas que aparecen a continuación? Si la pregunta no está respondida a la op satisfacción, ¿por qué aceptar. Yo diría que dejarlo abierto hasta que alguien le da una gran respuesta.
InformationsquelleAutor tintin | 2012-06-06

6 Comentarios

  1. 19

    Por supuesto, la segunda opción es la más elegante y simple. Pero si usted está preocupado por el rendimiento que usted debe ir para el primer enfoque, el segundo método también falla si un administrador de seguridad se está ejecutando.

    Voy a ir a por la primera opción si yo estuviera en su situación.
    También hay un error en su primera aproximación en la generación de hash
    se debe devolver el generador.toHashCode();
    en lugar de devolver el generador.hashCode (), que devuelve el hash de los objetos del generador de hash)

    • Puedo usar hashCode() en lugar de toHashCode(), como por la actualización de la aplicación de HashCodeBuilder, hashCode() método que ahora se llama toHashCode() commons.apache.org/lang/api-2.6/index.html?org/apache/commons/…
    • Dato divertido: EqualsBuilder.equals() no llama EqualsBuilder.isEquals(). No es que una mala trampa?! :-/
  2. 14

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

    La razón es simplemente el rendimiento. Después de ejecutar una pequeña prueba que he encontrado una gran diferencia de tiempo entre ellos.

    Con el fin de obtener una idea de tiempo, he creado este dos clases sencillas:

    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();
    };
    }

    Que la segunda clase es prácticamente el mismo que el primero, pero con diferentes equals 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 simples y sólo sirve para medir el tiempo.

    Los resultados fueron los siguientes:

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

    Me escogió para ser completamente iguales con el fin de tener la mayor de las veces. Si usted decide hacer la prueba con NotEquals condiciones va a tener menos tiempo, pero manteniendo una gran diferencia de tiempo demasiado.

    Puedo ejecutar esta prueba en una de 64 bits Intel Core i5-3317U de la CPU @1.70 GHz x4 con Fedora 21 y Eclipse de Luna.

    En conclusión, yo no habría riesgo de una gran diferencia de rendimiento con el fin de ahorrar un par de líneas de código que puede, posiblemente, no el tipo de todos modos el uso de una plantilla (en Eclipse en Windows -> Preferencias se encuentran 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();
    }
  3. 8

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

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

    2. Yo no compraría el rendimiento de los argumentos para la primera opción, a menos que estos incluyen una métrica relevante.
      E. g. cuántos milisegundos estaría basada en la reflexión «es igual a» agregar a una típica de un extremo a solicitud de latencia? En general, ¿qué % de aumento que sería?
      Sin saber que posibilidades existen de que la optimización es prematuro

  4. 3

    Su pregunta como está escrito claramente ilustra uno de los beneficios de la segundo método:

    En el primer caso, es muy fácil cometer el error return builder.hashCode(), en lugar de la correcta return builder.toHashCode(), lo que resultará en errores sutiles que puede ser muy difícil de localizar.

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

    • Este comentario es incorrecta para Apache Commons v2.5 o más reciente, Apache Commons v2.5 fue lanzado en febrero de 2010. Para citar el javadoc: «La calculada hashCode de toHashCode() se devuelve debido a la probabilidad de errores en la mal llamada toHashCode() y el unlikelyness de importar lo que la etiqueta para HashCodeBuilder sí es.». El javadoc está disponible aquí: commons.apache.org/proper/commons-lang/release-history.html
    • Es bueno saber que el error ha sido corregido. La respuesta sigue siendo relevante para las personas atascado utilizando la versión anterior de la biblioteca, aunque (sí, es de varios años, y la gente de debe actualizar, pero hay un montón de «enterprisey» lugares lento para mover ese tipo de cosas)
  5. 3

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

    1. No extensible. ¿Qué pasa si uno de los campo que usted está tratando de reivindicar la igualdad debe tratar a los nulos y en blanco como el de la igualdad?
    2. Debe mantener la lista de variables como si fueran codificados de las variables. Lo que significa que usted debe hacer una lista de todas las variables que se desea comparar. En este momento no hay ninguna diferencia entre un == o.getA() && b == o.getB() …
    3. El uso de la reflexión toma de recursos adicionales como usted ha señalado, y en aplicación de empresas que aplastar a los miles de millones de objetos. Haciendo esta reflexión es igual es tan malo como tener una pérdida de memoria.

    Voy a decir que existe la necesidad de un mejor marco que el Apache uno.

    • 1. Asegúrese de que las propiedades son NULOS o cadenas vacías (en las incubadoras, por ejemplo). Problema resuelto.
  6. 2

    De acuerdo con @Churk, Apache HashCodeBuilder y EqualsBuilder no está bien implementada. HashCodeBuilder sigue jugando con los números primos! Además, se hace una tremenda cantidad de trabajo innecesario. Tienen que leer 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 lugar de ello, el número de depósitos es una potencia de dos y el de más bajo orden N bits del código hash se utiliza para localizar el cubo.

    Además, se «mezcla» el código hash proporcionados por la aplicación, de modo que los bits están distribuidas de manera uniforme y, por lo tanto, los cubos se llena uniformemente.

    Por lo tanto, la forma correcta para reemplazar Objeto int.hashCode() está devolviendo el más simple, el valor de la constante con la más alta arity en la población de objetos que cohabitar en cualquier colección uso de la clase.

    Normalmente, el valor de ID de sin modificar es su mejor apuesta. Si el campo ID es integral, sólo echó a (int) y la devuelva. Si es una Cadena u otro Objeto, acabo de volver de su código hash. Usted consigue la idea. Para un compuesto identificador, devolver el campo (o el código hash de la misma), con la mayoría de los valores distintos. Menos es más.

    De curso, el contrato entre hashCode() y equals() que deben ser cumplidos. Por lo tanto, es igual a() debe ser implementado en consecuencia. hashCode() no necesita utilizar la totalidad de los calificadores necesaria para la igualdad, pero cualquiera de los campos utilizados en hashCode() debe ser utilizado en equals(). Aquí, métodos como StringUtils.es igual a( s1, s2 ) son útiles para el manejo de los valores null de forma consistente y segura.

    • Aún más importante: HashCodeBuilder.reflectionHashCode() y EqualsBuilder.reflectionEquals() son en realidad un poco peligroso en el que fácilmente se puede violar el «coherente» parte de la equals()/hashCode() contrato. He aquí lo que sucede: si se coloca un objeto en una tabla hash/set de algún tipo y, posteriormente, un campo que se utiliza en el hashCode() y equals() es cambiado, el objeto es huérfano eficazmente en la colección, y que nunca puede ser accedido. Sólo invariantes, los campos de identificador debe ser utilizado en el hashCode() y equals(). El programador no puede dejar esta opción para un genérico de «generador».
    • el cambio de los campos después de la inserción de objetos en una colección es un problema para cualquier iguala/hash aplicación

Dejar respuesta

Please enter your comment!
Please enter your name here