Que questões / armadilhas devem ser consideradas ao substituir equals
e hashCode
?
fonte
Que questões / armadilhas devem ser consideradas ao substituir equals
e hashCode
?
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: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
, seperson
é preguiçoso carregado, mesmo seperson.getName()
as forças de carga e retorna "John Doe". Na minha experiência, isso surge mais frequentemente emhashCode()
eequals()
.Se você estiver lidando com um ORM, use sempre getters e nunca use referências de campo em
hashCode()
eequals()
.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 emhashCode()
. Mas você pode usá-loequals()
.Um padrão que costumo usar é
Mas: você não pode incluir
getId()
nohashCode()
. Se o fizer, quando um objeto persistir, suashashCode
alterações. Se o objeto estiver em umHashSet
, você "nunca" o encontrará novamente.No meu
Person
exemplo, eu provavelmente usariagetName()
forhashCode
egetId()
plusgetName()
(apenas para paranóia) paraequals()
. Tudo bem se houver algum risco de "colisões"hashCode()
, mas nunca tudo bemequals()
.hashCode()
deve usar o subconjunto de propriedades não alteráveis deequals()
fonte
Saving an object will change it's state
!hashCode
deve retornarint
, então como você vai usargetName()
? 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 forA.equals(B) == true
,B.equals(A)
também deve retornartrue
. 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:
Fazendo
new A(1).equals(new A(1))
Além disso, onew 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:
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 deB
exemplo, mas verificar depois, porA
exemplo:Qual saída:
Onde, se
a
não for uma referênciaB
, pode ser uma referência à classeA
(porque você a estende), nesse caso vocêsuper.equals()
também chama .fonte
ThingWithOptionSetA
puder ser igual a uma,Thing
desde que todas as opções extras tenham valores padrão, e da mesma forma para aThingWithOptionSetB
, será possívelThingWithOptionSetA
comparar a igual a aThingWithOptionSetB
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ãob.equals(a) == true
ea.equals(b2) == true
masb.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:
Observe que equals () deve funcionar em hierarquias de herança para que o Princípio de Substituição de Liskov seja atendido.
fonte
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.
fonte
this
emthis.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.
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.
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! @. @
fonte
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 asjava.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étodosequals
e tambémcompareTo
devem ser consistentes. Aqui está um modelo para o método equals em umaComparable
classe:fonte
final
, e ocompareTo()
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 umainstanceof
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!
fonte
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.
Exemplo de saída de código:
fonte
Logicamente, temos:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒a.hashCode() == b.hashCode()
Mas não vice-versa!
fonte
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.
fonte
equals()
. Se um cientista louco criou uma duplicata de mim, seríamos equivalentes. Mas não teríamos o mesmo pai.