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.