Gere equals () e hashCode () com o Eclipse

Gere equals () e hashCode () com o Eclipse

*1. Introdução *

Neste artigo, exploramos a geração dos métodos _equals () _ e _hashCode () _ usando o Eclipse IDE. Ilustraremos o quão poderosa e conveniente é a geração automática de código do Eclipse e também enfatizaremos que testes diligentes de código ainda são necessários.

===* 2. Regras*

equals () _ em Java é usado para verificar se 2 objetos são equivalentes. Uma boa maneira de testar isso é garantir que os objetos sejam simétricos, reflexivos e transitivos. Ou seja, para três objetos não nulos _a, b e c:

  • Simétrico - a.equals (b) se e somente se b.equals (a)

  • Reflexivo - _a.equals (a) _

  • Transitivo - se _a.equals (b) _ e _b.equals (c) _ então _a.equals (c) _

_hashCode () _ deve obedecer a uma regra:

*2 objetos _equals () _ devem ter o mesmo valor _hashCode () _

===* 3. Classe com Primitivos *

Vamos considerar uma classe Java composta apenas de variáveis ​​de membros primitivas:

public class PrimitiveClass {

    private boolean primitiveBoolean;
    private int primitiveInt;

   //constructor, getters and setters
}

Utilizamos o IDE Eclipse para gerar equals () e hashCode () usando ‘Origem→ Gerar _hashCode () _ e _equals () _‘. O Eclipse fornece uma caixa de diálogo como esta:

link:/wp-content/uploads/2016/10/eclipse-equals-hascode.png [imagem:/wp-content/uploads/2016/10/eclipse-equals-hascode.png [eclipse-equals-hascode, width = 600, altura = 611]]

Podemos garantir que todas as variáveis ​​de membro sejam incluídas, escolhendo 'Selecionar tudo'.

Observe que as opções listadas abaixo do Ponto de inserção: afetam o estilo do código gerado. Aqui, não selecionamos nenhuma dessas opções, selecione 'OK' e os métodos são adicionados à nossa classe:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime* result + (primitiveBoolean ? 1231 : 1237);
    result = prime *result + primitiveInt;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    PrimitiveClass other = (PrimitiveClass) obj;
    if (primitiveBoolean != other.primitiveBoolean) return false;
    if (primitiveInt != other.primitiveInt) return false;
    return true;
}

O método _hashCode () _ gerado começa com uma declaração de um número primo (31), executa várias operações em objetos primitivos e retorna seu resultado com base no estado do objeto.

_equals () _ verifica primeiro se dois objetos são da mesma instância (==) e retorna true se forem.

Em seguida, verifica se o objeto de comparação é não nulo e os dois objetos são da mesma classe, retornando false se não forem.

Finalmente, _equals () _ verifica a igualdade de cada variável de membro, retornando false se algum deles não for igual.

Para que possamos escrever testes simples:

PrimitiveClass aObject = new PrimitiveClass(false, 2);
PrimitiveClass bObject = new PrimitiveClass(false, 2);
PrimitiveClass dObject = new PrimitiveClass(true, 2);

assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

===* 4. Aula com coleções e genéricos *

Agora, vamos considerar uma classe Java mais complexa com coleções e genéricos:

public class ComplexClass {

    private List<?> genericList;
    private Set<Integer> integerSet;

   //constructor, getters and setters
}

Novamente, usamos o Eclipse 'Source→ Generate hashCode () _ e _equals ()' . Observe que hashCode () _ usa _instanceOf para comparar objetos de classe, porque selecionamos 'Use' instanceof 'para comparar tipos' nas opções do Eclipse em o diálogo. Nós temos:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime* result + ((genericList == null)
      ? 0 : genericList.hashCode());
    result = prime *result + ((integerSet == null)
      ? 0 : integerSet.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (!(obj instanceof ComplexClass)) return false;
    ComplexClass other = (ComplexClass) obj;
    if (genericList == null) {
        if (other.genericList != null)
            return false;
    } else if (!genericList.equals(other.genericList))
        return false;
    if (integerSet == null) {
        if (other.integerSet != null)
            return false;
    } else if (!integerSet.equals(other.integerSet))
        return false;
    return true;
}

O método _hashCode () _ gerado depende dos métodos Java _AbstractList.hashCode () _ e _AbstractSet.hashCode () _ core. Eles iteram através de uma coleção, somando _hashCode () _ valores de cada item e retornando um resultado.

Da mesma forma, o método _equals () _ gerado usa _AbstractList.equals () _ e _AbstractSet.equals () _, que comparam coleções para igualdade comparando seus campos.

Podemos verificar a robustez testando alguns exemplos:

ArrayList<String> strArrayList = new ArrayList<String>();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));

ArrayList<String> strArrayListD = new ArrayList<String>();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet<Integer>(45,67));

assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

===* 5. Herança *

Vamos considerar as classes Java que usam herança:

public abstract class Shape {
    public abstract double area();

    public abstract double perimeter();
}

public class Rectangle extends Shape {
    private double width;
    private double length;

    @Override
    public double area() {
        return width* length;
    }

    @Override
    public double perimeter() {
        return 2 *(width + length);
    }
   //constructor, getters and setters
}

public class Square extends Rectangle {
    Color color;
   //constructor, getters and setters
}

Se tentarmos 'Source→ Generate hashCode () _ e _equals () _' na classe _Square, o Eclipse nos alertará que 'a superclasse' Rectangle 'não redeclarará _equals () _ e _hashCode () _: o código resultante pode não funcionar corretamente '.

Da mesma forma, recebemos um aviso sobre a superclasse ‘Shape 'quando tentamos gerar hashCode () _ e _equals () _ na classe _Rectangle.

O Eclipse nos permitirá avançar, apesar dos avisos. No caso de Rectangle, ele estende uma classe abstrata Shape que não pode implementar _hashCode () _ ou _equals () _ porque não possui variáveis ​​de membro concretas. Podemos ignorar o Eclipse nesse caso.

A classe Square, no entanto, herda variáveis ​​de membro width e length de Rectangle, bem como sua própria variável de cor. Criar hashCode () _ e _equals () _ no _Square sem primeiro fazer o mesmo para Rectangle significa usar apenas color em equals () _/ hashCode () _:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime* result + ((color == null) ? 0 : color.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null)
            return false;
    } else if (!color.equals(other.color))
        return false;
    return true;
}

Um teste rápido nos mostra que equals () _/ hashCode () _ para Square não são suficientes se apenas a width for diferente, porque width não está incluída nos cálculos de equals () _/ hashCode () _:

Square aObject = new Square(10, Color.BLUE);
Square dObject = new Square(20, Color.BLUE);

Assert.assertFalse(aObject.equals(dObject));
Assert.assertFalse(aObject.hashCode() == dObject.hashCode());

Vamos corrigir isso usando o Eclipse para gerar equals () _/ hashCode () _ para a classe Rectangle:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(length);
    result = prime *result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(width);
    result = prime* result + (int) (temp ^ (temp >>> 32));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Rectangle other = (Rectangle) obj;
    if (Double.doubleToLongBits(length)
      != Double.doubleToLongBits(other.length)) return false;
    if (Double.doubleToLongBits(width)
      != Double.doubleToLongBits(other.width)) return false;
    return true;
}

Devemos gerar novamente equals () _/ hashCode () _ na classe Square, para que Rectangle‘s equals () _/ hashCode () _ sejam chamados. Nesta geração de código, selecionamos todas as opções na caixa de diálogo Eclipse, para ver comentários, comparações instanceOf e blocos if:

@Override
public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime *result + ((color == null) ? 0 : color.hashCode());
    return result;
}


@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!super.equals(obj)) {
        return false;
    }
    if (!(obj instanceof Square)) {
        return false;
    }
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null) {
            return false;
       }
    } else if (!color.equals(other.color)) {
        return false;
    }
    return true;
}

Re-executando nosso teste de cima, passamos agora porque Square‘s hashCode () _/ equals () _ são calculados corretamente.

===* 6. Conclusão*

O IDE do Eclipse é muito poderoso e permite a geração automática de um código padrão - getters/setters, construtores de vários tipos, _equals () _ e _hashCode () _.

Ao entender o que o Eclipse está fazendo, podemos diminuir o tempo gasto nessas tarefas de codificação. No entanto, ainda devemos ter cuidado e verificar nosso código com testes para garantir que lidamos com todos os casos esperados.

Snippets de código, como sempre, podem ser encontrados over no GitHub.