Que questões / armadilhas devem ser consideradas ao substituir equals
e hashCode
?
Que questões / armadilhas devem ser consideradas ao substituir equals
e hashCode
?
Respostas:
equals()
( javadoc ) deve definir uma relação de equivalência (deve ser reflexiva , simétrica e transitiva ). Além disso, ele deve ser consistente (se os objetos não forem modificados, será necessário continuar retornando o mesmo valor). Além disso, o.equals(null)
deve sempre retornar falso.
hashCode()
( javadoc ) também deve ser consistente (se o objeto não for modificado em termos de equals()
, ele deve continuar retornando o mesmo valor).
A relação entre os dois métodos é:
Sempre
a.equals(b)
, entãoa.hashCode()
deve ser o mesmo queb.hashCode()
.
Se você substituir um, deverá substituir o outro.
Use o mesmo conjunto de campos que você usa equals()
para calcular hashCode()
.
Use as excelentes classes auxiliares EqualsBuilder e HashCodeBuilder da biblioteca Apache Commons Lang . Um exemplo:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
Ao usar uma coleção ou mapa baseado em hash , como HashSet , LinkedHashSet , HashMap , Hashtable ou WeakHashMap , verifique se o hashCode () dos principais objetos que você coloca na coleção nunca muda enquanto o objeto está na coleção. A maneira à prova de balas para garantir isso é tornar suas chaves imutáveis, o que também tem outros benefícios .
instanceof
retorna false se seu primeiro operando for nulo (Java efetivo novamente).
Há alguns problemas que vale a pena notar se você estiver lidando com classes persistentes usando um ORM (Object-Relationship Mapper) como o Hibernate, se você não achou que isso já era irracionalmente complicado!
Objetos preguiçosos carregados são subclasses
Se seus objetos persistirem usando um ORM, em muitos casos, você estará lidando com proxies dinâmicos para evitar carregar o objeto muito cedo no armazenamento de dados. Esses proxies são implementados como subclasses de sua própria classe. Isso significa que this.getClass() == o.getClass()
retornará false
. Por exemplo:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Se você está lidando com um ORM, usar o instanceof Person
é a única coisa que se comportará corretamente.
Objetos carregados preguiçosos têm campos nulos
Os ORMs geralmente usam os getters para forçar o carregamento de objetos carregados com preguiça. Isto significa que person.name
será null
, se person
é preguiçoso carregado, mesmo se person.getName()
as forças de carga e retorna "John Doe". Na minha experiência, isso surge mais frequentemente em hashCode()
e equals()
.
Se você estiver lidando com um ORM, use sempre getters e nunca use referências de campo em hashCode()
e equals()
.
Salvar um objeto mudará seu estado
Objetos persistentes geralmente usam um id
campo para manter a chave do objeto. Este campo será atualizado automaticamente quando um objeto for salvo pela primeira vez. Não use um campo de identificação em hashCode()
. Mas você pode usá-lo equals()
.
Um padrão que costumo usar é
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Mas: você não pode incluir getId()
no hashCode()
. Se o fizer, quando um objeto persistir, suas hashCode
alterações. Se o objeto estiver em um HashSet
, você "nunca" o encontrará novamente.
No meu Person
exemplo, eu provavelmente usaria getName()
for hashCode
e getId()
plus getName()
(apenas para paranóia) para equals()
. Tudo bem se houver algum risco de "colisões" hashCode()
, mas nunca tudo bem equals()
.
hashCode()
deve usar o subconjunto de propriedades não alteráveis de equals()
Saving an object will change it's state
! hashCode
deve retornar int
, então como você vai usar getName()
? Você pode dar um exemplo para o seuhashCode
Um esclarecimento sobre o obj.getClass() != getClass()
.
Essa afirmação é o resultado de equals()
ser uma herança hostil. O JLS (especificação da linguagem Java) especifica que, se for A.equals(B) == true
, B.equals(A)
também deve retornar true
. Se você omitir essa instrução que herda classes que substituemequals()
(e alteram seu comportamento) quebrará essa especificação.
Considere o seguinte exemplo do que acontece quando a instrução é omitida:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
Fazendo new A(1).equals(new A(1))
Além disso, o new B(1,1).equals(new B(1,1))
resultado é verdadeiro, como deveria.
Parece tudo muito bom, mas veja o que acontece se tentarmos usar as duas classes:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Obviamente, isso está errado.
Se você deseja garantir a condição simétrica. a = b se b = a e o princípio de substituição de Liskov chamar super.equals(other)
não apenas no caso de B
exemplo, mas verificar depois, por A
exemplo:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
Qual saída:
a.equals(b) == true;
b.equals(a) == true;
Onde, se a
não for uma referência B
, pode ser uma referência à classe A
(porque você a estende), nesse caso você super.equals()
também chama .
ThingWithOptionSetA
puder ser igual a uma, Thing
desde que todas as opções extras tenham valores padrão, e da mesma forma para a ThingWithOptionSetB
, será possível ThingWithOptionSetA
comparar a igual a a ThingWithOptionSetB
apenas se todas as propriedades não básicas de ambos os objetos corresponderem aos padrões, mas Não vejo como você testa isso.
B b2 = new B(1,99)
, então b.equals(a) == true
e a.equals(b2) == true
mas b.equals(b2) == false
.
Para uma implementação amigável à herança, confira a solução de Tal Cohen, Como implemento corretamente o método equals ()?
Resumo:
Em seu livro Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch afirma que "simplesmente não há como estender uma classe instável e adicionar um aspecto, preservando o contrato igual". Tal discorda.
Sua solução é implementar equals () chamando outro não-simétrico blindlyEquals () nos dois sentidos. blindlyEquals () é substituído por subclasses, equals () é herdado e nunca substituído.
Exemplo:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Observe que equals () deve funcionar em hierarquias de herança para que o Princípio de Substituição de Liskov seja atendido.
if (this.getClass() != o.getClass()) return false
, mas flexível, na medida em que só retorna false se a (s) classe (s) derivada (s) se preocupam em modificar iguais. Isso está certo?
Ainda espantado que ninguém recomendasse a biblioteca de goiaba para isso.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
this
em this.getDate()
meios nada (excepto desordem)
if (!(otherObject instanceof DateAndPattern)) {
. Concordo com hernan e Steve Kuo (apesar de ser uma questão de preferência pessoal), mas com +1.
Existem dois métodos na superclasse como java.lang.Object. Precisamos substituí-los pelo objeto personalizado.
public boolean equals(Object obj)
public int hashCode()
Objetos iguais devem produzir o mesmo código de hash, desde que iguais, porém objetos desiguais não precisam produzir códigos de hash distintos.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
Se você quiser obter mais, consulte este link como http://www.javaranch.com/journal/2002/10/equalhash.html
Este é outro exemplo, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Diverta-se! @. @
Existem algumas maneiras de verificar sua igualdade de classe antes de verificar a igualdade de membros, e acho que ambas são úteis nas circunstâncias certas.
instanceof
operador.this.getClass().equals(that.getClass())
.Uso o número 1 em uma final
implementação igual ou ao implementar uma interface que prescreve um algoritmo para iguais (como as java.util
interfaces de coleta - a maneira correta de verificar com(obj instanceof Set)
ou interface que você está implementando). Geralmente é uma má escolha quando os iguais podem ser substituídos porque isso quebra a propriedade de simetria.
A opção 2 permite que a classe seja estendida com segurança, sem anular iguais ou quebrar a simetria.
Se sua classe também for Comparable
, os métodos equals
e também compareTo
devem ser consistentes. Aqui está um modelo para o método equals em uma Comparable
classe:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
, e o compareTo()
método foi substituído para reverter a ordem de classificação, as instâncias da subclasse e superclasse não devem ser consideradas iguais. Quando esses objetos foram usados juntos em uma árvore, as chaves "iguais" de acordo com uma instanceof
implementação podem não ser encontradas.
Para iguais, procure Secrets of Equals de Angelika Langer . Eu amo muito isso. Ela também é uma ótima FAQ sobre genéricos em Java . Veja seus outros artigos aqui (role para baixo até "Core Java"), onde ela também continua com a Parte 2 e a "comparação de tipos mistos". Divirta-se lendo-os!
O método equals () é usado para determinar a igualdade de dois objetos.
como valor int de 10 é sempre igual a 10. Mas esse método equals () trata da igualdade de dois objetos. Quando dizemos objeto, ele terá propriedades. Para decidir sobre igualdade, essas propriedades são consideradas. Não é necessário que todas as propriedades sejam levadas em consideração para determinar a igualdade e, com relação à definição e contexto da classe, ela pode ser decidida. Em seguida, o método equals () pode ser substituído.
devemos sempre substituir o método hashCode () sempre que substituirmos o método equals (). Se não, o que vai acontecer? Se usarmos hashtables em nosso aplicativo, ele não se comportará conforme o esperado. Como o hashCode é usado na determinação da igualdade de valores armazenados, ele não retornará o valor correspondente correto para uma chave.
A implementação padrão fornecida é o método hashCode () na classe Object, que usa o endereço interno do objeto e o converte em número inteiro e o retorna.
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
Exemplo de saída de código:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Um problema que encontrei é que dois objetos contêm referências um ao outro (um exemplo é um relacionamento pai / filho com um método de conveniência no pai para obter todos os filhos).
Esses tipos de coisas são bastante comuns ao fazer mapeamentos do Hibernate, por exemplo.
Se você incluir as duas extremidades do relacionamento em seu hashCode ou em testes iguais, é possível entrar em um loop recursivo que termina em StackOverflowException.
A solução mais simples é não incluir a coleção getChildren nos métodos.
equals()
. Se um cientista louco criou uma duplicata de mim, seríamos equivalentes. Mas não teríamos o mesmo pai.