Substituindo o método java equals () - não está funcionando?

150

Corri para um problema interessante (e muito frustrante) com o equals()método hoje, que causou o travamento do que eu pensava ser uma classe bem testada e causou um bug que levou muito tempo para ser rastreado.

Apenas para completar, eu não estava usando um IDE ou depurador - apenas um bom editor de texto à moda antiga e System.out. O tempo era muito limitado e era um projeto escolar.

De qualquer forma -

Eu estava desenvolvendo um carrinho de compras básico que poderia conter um ArrayListdos Bookobjetos . A fim de implementar as addBook(), removeBook()e hasBook()métodos do carrinho, eu queria verificar se o Bookjá existia no Cart. Então eu vou -

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

Tudo funciona bem nos testes. Crio 6 objetos e os preencho com dados. Faça muitas adições, remoções, operações com () Carte tudo funciona bem. Eu li que você pode ter equals(TYPE var)ouequals(Object o) { (CAST) var } presumiu que, como estava funcionando, não importava muito.

Então eu corri para um problema - eu precisava para criar um Bookobjeto com única a IDnele de dentro da classe Book. Nenhum outro dado seria inserido nele. Basicamente o seguinte:

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, o equals(Book b)método não funciona mais. Demorou MUITO tempo para rastrear sem um bom depurador e assumindo que a Cartclasse foi testada e correta corretamente. Depois de trocar o equals()método para o seguinte:

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

Tudo começou a funcionar novamente. Existe uma razão pela qual o método decidiu não usar o parâmetro Book, mesmo que claramente fosse um Bookobjeto? A única diferença parecia ser instanciada na mesma classe e preenchida apenas com um membro dos dados. Estou muito, muito confusa. Por favor, lançar alguma luz?

Josh Smeaton
fonte
1
Estou ciente de que violei o 'Contrato' em relação à substituição dos métodos iguais por ser reflexivo - no entanto, precisava de uma maneira rápida de verificar se o objeto existia na ArrayList sem usar genéricos.
Josh Smeaton
1
Esta é uma boa lição para aprender sobre Java e iguais
jjnguy

Respostas:

329

Em Java, o equals()método herdado Objecté:

public boolean equals(Object other);

Em outras palavras, o parâmetro deve ser do tipo Object. Isso é chamado de substituição ; seu método public boolean equals(Book other)faz o que é chamado de sobrecarga para o equals()método.

Os métodos ArrayListsubstituídos são usados equals()para comparar o conteúdo (por exemplo, para os métodos contains()e equals()) e não para os sobrecarregados. Na maioria do seu código, chamar o que não substituiu corretamente Objectos iguais foi bom, mas não compatível ArrayList.

Portanto, não substituir o método corretamente pode causar problemas.

Eu substituo é igual ao seguinte sempre:

@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...
}

O uso da @Overrideanotação pode ajudar bastante com erros tolos.

Use-o sempre que achar que está substituindo o método de uma superclasse ou interface. Dessa forma, se você fizer da maneira errada, receberá um erro de compilação.

jjnguy
fonte
31
Este é um bom argumento a favor da anotação @Override ... se o OP tinha uso @Override seu compilador teria dito a ele que ele não estava realmente substituir um método classe pai ...
Cowan
1
Nunca esteve ciente do @Override, obrigado por isso! Também gostaria de acrescentar que a substituição de hashCode () realmente deveria ter sido feita e pode ter detectado o erro mais cedo.
Josh Smeaton
5
Alguns IDEs (por exemplo, Eclipse) podem até gerar automaticamente os métodos equals () e hashcode () para você com base nas variáveis ​​de membro da classe.
sk.
1
if (!(other instanceof MyClass))return false;retorna falsese MyClassestende a outra classe. Mas não voltaria falsese a outra classe se estendesse MyClass. Não deveria equalser menos contraditório?
Robert
19
Ao usar instanceof, a verificação nula anterior é redundante.
Mateusz Dymczyk 02/02
108

Se você usa eclipse, basta ir ao menu superior

Fonte -> Gerar equals () e hashCode ()

Fred
fonte
Concordo! Essa eu nunca soube antes e gerando torna menos propenso a erros
Boy
O mesmo aqui. Obrigado Fred!
Anila
16
No IntelliJ, você encontra isso em Código → Gerar… ou control + N. :)
rightfold
No Netbeans, você acessa a barra de menus> Origem (ou clique com o botão direito do mouse)> Inserir código (ou Ctrl-I) e clique em Gerar iguais () ...
Salomão
11

Ligeiramente fora de tópico para sua pergunta, mas provavelmente vale a pena mencionar de qualquer maneira:

O Commons Lang possui alguns métodos excelentes que você pode usar para substituir iguais e hashcode. Confira EqualsBuilder.reflectionEquals (...) e HashCodeBuilder.reflectionHashCode (...) . Me salvou muita dor de cabeça no passado - embora, é claro, se você quiser apenas "igual" ao ID, ele pode não se encaixar nas suas circunstâncias.

Concordo também que você deve usar a @Overrideanotação sempre que substituir valores iguais (ou qualquer outro método).


fonte
4
Se você é um usuário do eclipse, você também pode ir right click -> source -> generate hashCode() and equals(),
tunaranch
1
Estou certo de que esse método é executado em tempo de execução? Não teremos problemas de desempenho, se percorrermos uma grande coleção com itens que verificam se há igualdade em relação a algum outro item por causa da reflexão?
Gaket
4

Outra solução rápida que salva o código padrão é a anotação Lombok EqualsAndHashCode . É fácil, elegante e personalizável. E não depende do IDE . Por exemplo;

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;

Veja as opções disponíveis para personalizar quais campos usar nos iguais. Lombok está disponível em maven . Basta adicioná-lo com o escopo fornecido :

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

no Android Studio é alt + insert ---> equals e hashCode

Exemplo:

    @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();
}
David Hackro
fonte
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...
bcsb1001
fonte
1
@Elazar Como assim? objé declarado como um Object. O ponto da herança é que você pode atribuir um Booka obj. Depois disso, a menos que você sugira que um Objectnão deve ser comparável a um Stringvia equals(), esse código deve ser perfeitamente legal e retornar false.
bcsb1001
Eu sugiro exatamente isso. Eu acredito que é bastante aceito.
Elazar
0

a instanceOfdeclaração é freqüentemente usada na implementação de iguais.

Esta é uma armadilha popular!

O problema é que o uso instanceOfviola a regra da simetria:

(object1.equals(object2) == true) se e apenas se (object2.equals(object1))

se o primeiro igual é verdadeiro e o objeto2 é uma instância de uma subclasse da classe à qual o obj1 pertence, o segundo igual retornará falso!

se a classe considerada à qual ob1 pertence for declarada como final, esse problema não poderá surgir, mas, em geral, você deve testar da seguinte maneira:

this.getClass() != otherObject.getClass(); Caso contrário, retorne false; caso contrário, teste os campos para comparar a igualdade!

Nikel8000
fonte
3
Consulte Bloch, Effective Java, Item 8, uma grande seção que discute problemas com a substituição do equals()método. Ele recomenda não usar getClass(). A principal razão é que isso infringe o Princípio de Substituição de Liskov para subclasses que não afetam a igualdade.
Stuart Marks
-1

recordId é propriedade do 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;
    }
vootla561
fonte