2008-10-09 26 views
143

Me encontré con un problema interesante (y muy frustrante) con el método equals() hoy que causó que se bloqueara lo que pensé que era una clase bien probada y causó un error que me llevó mucho tiempo hora de localizar.Sobrescribir la peculiaridad del método java equals()

Para completar, no estaba usando un IDE o un depurador, solo un buen editor de texto antiguo y System.out's. El tiempo fue muy limitado y fue un proyecto escolar.

De todos modos -

que estaba desarrollando un carrito de la compra básica que podría contener un ArrayList de Book objetos. Para implementar los métodos addBook(), removeBook() y hasBook() del carro, quería verificar si el Book ya existía en el Cart. Así que voy -

public boolean equals(Book b) { 
    ... // More code here - null checks 
    if (b.getID() == this.getID()) return true; 
    else return false; 
} 

Todo funciona bien en las pruebas. Creo 6 objetos y los llevo con datos. Realiza muchas operaciones de adición, eliminación, has() en el Cart y todo funciona bien. Leí que puede tener equals(TYPE var) o equals(Object o) { (CAST) var }, pero asumió que, dado que funcionaba, no importaba demasiado.

Entonces me encontré con un problema - que necesitaba para crear un objeto con Bookúnica la ID en ella desde dentro de la clase de libro. No se ingresarían otros datos. Básicamente lo siguiente:

public boolean hasBook(int i) { 
    Book b = new Book(i); 
    return hasBook(b); 
} 

public boolean hasBook(Book b) { 
    // .. more code here 
    return this.books.contains(b); 
} 

De repente, el método equals(Book b) ya no funciona. Esto llevó MUCHO tiempo en rastrear sin un buen depurador y suponiendo que la clase Cart se había probado correctamente y era correcta. Después Swaapping el método equals() a lo siguiente:

public boolean equals(Object o) { 
    Book b = (Book) o; 
    ... // The rest goes here 
} 

Todo comenzó a trabajar de nuevo. ¿Hay alguna razón por la que el método decidió no tomar el parámetro Book aunque claramente era un objeto Book? La única diferencia parecía ser que se creaba una instancia dentro de la misma clase, y solo se llenaba con un miembro de datos. Estoy muy confundido. Por favor, arroja algo de luz?

+0

Soy aw es que violé el 'Contrato' sobre la anulación de los métodos iguales al ser reflexivo; sin embargo, necesitaba una forma rápida de verificar si el objeto existía en el ArrayList sin usar genéricos. –

+0

Esta es una buena lección para aprender sobre Java y es igual a – jjnguy

+5

No puedo ver cómo un carrito de compras puede ser igual a un libro. Quiero decir, ¿cómo puede 'hasBook (Book b)' devolver el resultado de 'this.equals (b)'? – Robert

Respuesta

304

En Java, el método equals() que se hereda de Object es:

public boolean equals(Object other); 

En otras palabras, el parámetro debe ser de tipo Object.

El ArrayList utiliza el método equal correcto, donde siempre llamaba al que no anulaba correctamente los equivalentes de Object.

No anular el método correctamente puede causar problemas.

I anulación es igual a la siguiente cada vez que:

@Override 
public boolean equals(Object other){ 
    if (other == null) return false; 
    if (other == this) return true; 
    if (!(other instanceof MyClass))return false; 
    MyClass otherMyClass = (MyClass)other; 
    ...test other properties here... 
} 

El uso de la @Override anotación puede ayudar a un montón de errores tontos.

Úselo siempre que crea que está anulando una super clase o el método de la interfaz. De esta forma, si lo haces mal, obtendrás un error de compilación.

+29

Este es un buen argumento a favor de la anotación @Override ... si el OP hubiera usado @Override su compilador le hubiera dicho que en realidad no estaba anulando un método de clase padre ... – Cowan

+0

Totalmente de acuerdo !!!! ! Agregaré eso a mi respuesta en un sec – jjnguy

+1

Nunca tuve conocimiento de @Override, ¡gracias por eso! También me gustaría añadir que la anulación de hashCode() realmente debería haberse hecho y puede haber detectado el error antes. –

11

ligeramente fuera de tema a su pregunta, pero es probable que de todos modos vale la pena mencionar:

Commons Lang tiene algunos excelentes métodos que puede utilizar en anulando iguales y código hash. Consulte EqualsBuilder.reflectionEquals(...) y HashCodeBuilder.reflectionHashCode(...). Me ha ahorrado muchos dolores de cabeza en el pasado, aunque, por supuesto, si solo quieres hacer un "igual" en la identificación, puede que no encaje en tus circunstancias.

También estoy de acuerdo con que debe usar la anotación @Override siempre que esté anulando iguales (o cualquier otro método).

+4

Si eres un usuario de eclipse, también puedes ir 'clic derecho -> fuente -> generar hashCode() y es igual a() ', – tunaranch

+0

¿Tengo razón en que este método se ejecuta en tiempo de ejecución? ¿No tendremos problemas de rendimiento en caso de que atravesemos una gran colección con elementos que verifiquen su igualdad con algún otro elemento debido a la reflexión? – Gaket

95

Si utilizar Eclipse sólo tiene que ir al menú superior

Fuente -> Generar equals() y hashCode ()

+7

Ojalá pudiera dar 10 votos por respuesta. – dansalmo

+0

¡Estoy de acuerdo! Este que nunca antes conocí y lo genera lo hace menos propenso a errores – Boy

+0

Lo mismo aquí. Gracias Fred! – Anila

4

Otra solución rápida que ahorra código repetitivo es Lombok EqualsAndHashCode annotation. Es fácil, elegante y personalizable. Y no depende del IDE. Por ejemplo;

import lombok.EqualsAndHashCode; 

@EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals. 
public class ErrorMessage{ 

    private long  errorNumber; 
    private int   numberOfParameters; 
    private Level  loggingLevel; 
    private String  messageCode; 

Véase el options disponible para personalizar los campos que desea utilizar en los iguales. Lombok está disponible en maven. Sólo tiene que añadir con proporcionado alcance:

<dependency> 
    <groupId>org.projectlombok</groupId> 
    <artifactId>lombok</artifactId> 
    <version>1.14.8</version> 
    <scope>provided</scope> 
</dependency> 
1

la declaración instanceOf se utiliza a menudo en la aplicación de iguales.

¡Esto es una trampa popular!

El problema es que el uso de instanceOf viola la regla de simetría:

(object1.equals(object2) == true)si y sólo si(object2.equals(object1))

si la primera es igual es verdad, y objeto2 es una instancia de una subclase de la clase a la que pertenece obj1, ¡entonces el segundo igual devolverá falso!

si la clase considerada en ob1 pertenece a se declara como final, entonces este problema no puede surgir, pero en general, se debe probar la siguiente manera:

this.getClass() != otherObject.getClass(); si no, devuelve falso, de lo contrario la prueba la campos para comparar para la igualdad!

+2

Ver Bloch, * Effective Java, * Item 8, una gran sección que discute problemas al anular el método 'equals()'. Él recomienda no usar 'getClass()'.La razón principal es que al hacerlo se rompe el Principio de Sustitución de Liskov por subclases que no afectan la igualdad. –

0

en Android Studio es alt + insertar ---> iguales y hashCode

Ejemplo:

@Override 
public boolean equals(Object o) { 
    if (this == o) return true; 
    if (o == null || getClass() != o.getClass()) return false; 

    Proveedor proveedor = (Proveedor) o; 

    return getId() == proveedor.getId(); 

} 

@Override 
public int hashCode() { 
    return getId(); 
} 
-1

recordId es propiedad del objeto

@Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null) 
      return false; 
     if (getClass() != obj.getClass()) 
      return false; 
     Nai_record other = (Nai_record) obj; 
     if (recordId == null) { 
      if (other.recordId != null) 
       return false; 
     } else if (!recordId.equals(other.recordId)) 
      return false; 
     return true; 
    } 
1

considerar:

Object obj = new Book(); 
obj.equals("hi"); 
// Oh noes! What happens now? Can't call it with a String that isn't a Book... 
+0

Eso sería una buena cosa. – Elazar

+1

@Elazar ¿Cómo es eso? 'obj' se declara como un' Objeto'. El punto de herencia es que puedes asignar un 'Libro' a' obj'. Después de eso, a menos que sugieras que un 'Objeto' no debe ser comparable a un' String' vía 'igual()', este código debería ser perfectamente legal y devolver 'false'. – bcsb1001

+0

Sugiero exactamente eso. Creo que es bastante ampliamente aceptado. – Elazar

Cuestiones relacionadas