Como implementar o método equals?

2014/10/05

O operador == sempre compara a identidade, assim como a implementação default do método equals.

As vezes a implementação default do equals possui o comportamento desejado (como por exemplo numa type-safe enumeration), mas o equals deveria geralmente comparar o estado, não a identidade. Isso é particularmente verdade para classes data-centric que mapeiam registros de bancos de dados.

hashCode e equals possuem uma relação próxima:

Objetos inseridos em Lists, Sets, ou Maps (tanto como chave quanto como valor) deveriam ter uma definição apropriada do equals.

Se você extende uma classe concreta e adiciona um novo campo que contribui para o equals, então não é possível escrever um equals perfeitamente correto para esta classe nova. Ao invés disso, você deveria usar composição ao invés de herança.

Ao implementar o equals, os campos são comparados de forma diferente dependendo do seu tipo:

É importante notar que se um campo é do tipo wrapper class, então a implementação do equals é simples, pois ela só faz uma coisa: delega a invocação do equals.

Em um método equals é, geralmente, melhor ordenar as comparações dos campos de acordo com a significância do campo, isto é, campos mais significativos, deveriam ser avaliados primeiro. Isso permite que o operador lógico && minimize o tempo de execução.

Abaixo temos um exemplo padrão de implementação do método equals que leva em consideração cada atributo, este código é análogo aos algoritmos gerados pelas IDE's eclipse e NetBeans.

import java.util.Arrays;

class MinhaClasse {

    private int      atributo1;
    private Object   atributo2;
    private String[] atributo3;

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof MinhaClasse)) {
            return false;
        }
        MinhaClasse other = (MinhaClasse) obj;
        if (atributo1 != other.atributo1) {
            return false;
        }
        if (atributo2 == null) {
            if (other.atributo2 != null) {
                return false;
            }
        } else if (!atributo2.equals(other.atributo2)) {
            return false;
        }
        if (!Arrays.equals(atributo3, other.atributo3)) {
            return false;
        }
        return true;
    }
}

Percebam que é um código bastante extenso e sua manutenção exige bastante atenção, principalmente se a classe for alterada para possuir mais atributos que possam influenciar na computação do equals.

Tendo em vista as peculiaridades acerca do método equals, disponibilizei em meu repositório a classe EqualsUtils que simplifica a implementação do método equals.

Abaixo, um exemplo prático de como utilizar a classe EqualsUtils para implementar o método equals da classe acima:

import static br.com.staroski.equality.EqualsUtils.*;
 
class MinhaClasse {

    private int      atributo1;
    private Object   atributo2;
    private String[] atributo3;

    public boolean equals(Object object){
        if (this == object) {
            return true;
        }
        if (object instanceof MinhaClasse) {
            MinhaClasse that = (MinhaClasse) object;
            return equal(this.atributo1, that.atributo1)
                && equal(this.atributo2, that.atributo2)
                && equal(this.atributo3, that.atributo3);
        }
        return false; 
    }
}