Esse problema é mais aparente quando você tem implementações diferentes de uma interface e, para os propósitos de uma coleção específica, você se preocupa apenas com a exibição dos objetos no nível da interface. Por exemplo, suponha que você tenha uma interface como esta:
public interface Person {
int getId();
}
A maneira usual de implementar hashcode()
e equals()
implementar classes teria código como este no equals
método:
if (getClass() != other.getClass()) {
return false;
}
Isso causa problemas quando você combina implementações de Person
em a HashMap
. Se o HashMap
único se importar com a visualização no nível da interface Person
, poderá acabar com duplicatas que diferem apenas nas classes de implementação.
Você pode fazer esse caso funcionar usando o mesmo equals()
método liberal para todas as implementações, mas corre o risco de equals()
fazer a coisa errada em um contexto diferente (como comparar dois Person
s suportados por registros de banco de dados com números de versão).
Minha intuição me diz que igualdade deve ser definida por coleção em vez de por classe. Ao usar coleções que dependem de pedidos, você pode usar um costume Comparator
para escolher a ordem certa em cada contexto. Não há análogo para coleções baseadas em hash. Por que é isso?
Apenas para esclarecer, essa pergunta é distinta de " Por que .compareTo () está em uma interface, enquanto .equals () está em uma classe em Java? ", Porque lida com a implementação de coleções. compareTo()
e equals()
/ hashcode()
ambos sofrem com o problema da universalidade ao usar coleções: você não pode escolher diferentes funções de comparação para coleções diferentes. Portanto, para os propósitos desta pergunta, a hierarquia de herança de um objeto não importa; o que importa é se a função de comparação é definida por objeto ou por coleção.
fonte
Person
implementar o esperadoequals
e ohashCode
comportamento. Você então teria umHashMap<PersonWrapper, V>
. Este é um exemplo em que uma abordagem de POO puro não é elegante: nem toda operação em um objeto faz sentido como método desse objeto. OObject
tipo inteiro de Java é um amálgama de responsabilidades diferentes - apenas os métodosgetClass
,finalize
etoString
parecem remotamente justificáveis pelas melhores práticas de hoje.IEqualityComparer<T>
para uma coleção baseada em hash. Se você não especificar um, ele usará uma implementação padrão baseada emObject.Equals
eObject.GetHashCode()
. 2) A substituição da IMOEquals
em um tipo de referência mutável raramente é uma boa idéia. Dessa forma, a igualdade padrão é bastante rigorosa, mas você pode usar uma regra de igualdade mais relaxada quando precisar através de um costumeIEqualityComparer<T>
.Respostas:
Este projeto é conhecido como "Igualdade Universal", é a crença de que se duas coisas são iguais ou não, é uma propriedade universal.
Além disso, a igualdade é uma propriedade de dois objetos, mas no OO, você sempre chama um método em um único objeto , e esse objeto decide apenas como lidar com essa chamada de método. Portanto, em um design como o de Java, em que igualdade é uma propriedade de um dos dois objetos que estão sendo comparados, não é possível garantir algumas propriedades básicas de igualdade, como simetria (
a == b
⇔b == a
), porque, no primeiro caso, o método está sendo chamadoa
e, no segundo caso,b
e devido aos princípios básicos da OO, éa
uma decisão exclusiva (no primeiro caso) oub
decisão (no segundo caso) se considera ou não igual ao outro. A única maneira de obter simetria é cooperar com os dois objetos, mas se não o fizerem ... azar.Uma solução seria fazer da igualdade não uma propriedade de um objeto, mas uma propriedade de dois objetos ou uma propriedade de um terceiro objeto. Essa última opção também resolve o problema da igualdade universal, porque se você tornar a igualdade uma propriedade de um terceiro objeto de "contexto", poderá imaginar ter
EqualityComparer
objetos diferentes para contextos diferentes.Esse é o design escolhido para Haskell, por exemplo, com a
Eq
classe de tipo. Também é o design escolhido por algumas bibliotecas Scala de terceiros (ScalaZ, por exemplo), mas não a biblioteca principal ou padrão do Scala, que usa igualdade universal para compatibilidade com a plataforma host subjacente.É, curiosamente, também o design escolhido com as interfaces
Comparable
/ JavaComparator
. Os projetistas de Java estavam claramente cientes do problema, mas por algum motivo o resolveram apenas por pedidos, mas não por igualdade (ou hash).Então, quanto à questão
a resposta é "eu não sei". Claramente, os projetistas de Java estavam cientes do problema, como evidenciado pela existência de
Comparator
, mas obviamente não consideravam um problema de igualdade e hash. Outras linguagens e bibliotecas fazem escolhas diferentes.fonte
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
ou seja, ambosequals
ehashCode
foram introduzidos comoObject
métodos pelos desenvolvedores Java apenas por uma questão deHashtable
- não há noção de UE nem nada de silimar em nenhum lugar da especificação, e a citação é clara o suficiente para mim; se não fosse oHashtable
,equals
provavelmente teria sido em uma interface comoComparable
. Como tal, embora eu acreditasse anteriormente que sua resposta estava correta, agora a considero sem fundamento.clone
- era originalmente um operador , não um método (consulte Oak Language Specification), citação:The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)
- os três operadores semelhantes a palavras-chave eraminstanceof new clone
(seção 8.1, operadores). Suponho que essa é a verdadeira razão (histórica) da bagunçaclone
/Cloneable
-Cloneable
foi simplesmente uma invenção posterior, e oclone
código existente foi adaptado a ela.A verdadeira resposta para
é, citação cortesia de Josh Bloch :
O problema reside unicamente na história do Java, como com outros assuntos semelhantes, por exemplo,
.clone()
vsCloneable
.tl; dr
é principalmente por razões históricas; o comportamento / abstração atual foi introduzido no JDK 1.0 e não foi corrigido posteriormente porque era praticamente impossível fazê-lo com a manutenção da compatibilidade com o código anterior.
Primeiro, vamos resumir alguns fatos Java conhecidos:
Hashtable
,.hashCode()
&.equals()
foram implementados no JDK 1.0, ( Hashtable )Comparable
/Comparator
foi introduzido no JDK 1.2 ( comparável ),Agora, segue:
.hashCode()
e.equals()
interfaces distintas, mantendo a compatibilidade com versões anteriores, depois que as pessoas perceberam que há abstrações melhores do que colocá-las em superobjetos, porque, por exemplo, todo e qualquer programador Java da 1.2 sabia que cadaObject
um deles os possuía, e eles tinham ficar lá fisicamente para fornecer compatibilidade com código compilado (JVM) também - e adicionar uma interface explícita a todas asObject
subclasses que realmente as implementaram tornaria essa bagunça igual (sic!) aClonable
uma ( Bloch discute por que Cloneable é péssimo , também discutido em, por exemplo, EJ 2nd e muitos outros lugares, incluindo SO),Agora, você pode perguntar "o que
Hashtable
tem tudo isso"?A resposta é:
hashCode()
/equals()
contrato e habilidades de design de linguagem não tão boas dos principais desenvolvedores de Java em 1995/1996.Citação de Java 1.0 Language Spec, datado de 1996 - 4.3.2 The Class
Object
, p.41:(note que essa declaração exata foi alterado em versões posteriores, quer dizer, a citar:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, tornando-se impossível fazer a diretaHashtable
-hashCode
-equals
conexão sem ler JLS históricos!)A equipe Java decidiu que queria uma boa coleção no estilo de dicionário e criou
Hashtable
(boa ideia até agora), mas queria que o programador fosse capaz de usá-la com o mínimo de curva de código / aprendizado possível (opa! Problemas ao receber!) - e, como ainda não havia genéricos [afinal de contas, é o JDK 1.0], isso significaria que todos osObject
envolvidosHashtable
teriam que implementar explicitamente alguma interface (e as interfaces ainda estavam no seu início naquela época ...Comparable
ainda nem!) , tornando isso um impedimento para usá-lo para muitos - ouObject
teria que implementar implicitamente algum método de hash.Obviamente, eles foram com a solução 2, pelas razões descritas acima. Sim, agora sabemos que eles estavam errados. ... é fácil ser inteligente em retrospectiva. rir
Agora,
hashCode()
exige que todo objeto que o possua tenha umequals()
método distinto - portanto, era óbvio queequals()
ele também deveria ser colocadoObject
.Desde as padrão implementações destes métodos sobre válida
a
&b
Object
s são essencialmente inútil por ser redundante (tornandoa.equals(b)
igual aa==b
ea.hashCode() == b.hashCode()
aproximadamente igual aa==b
também, a menos quehashCode
e / ouequals
é anulado, ou você GC centenas de milhares deObject
s durante o ciclo de vida de sua aplicação 1 ) , é seguro dizer que eles foram fornecidos principalmente como medida de backup e por conveniência de uso. É exatamente assim que chegamos ao fato conhecido de que sempre substitui os dois.equals()
e.hashCode()
se você pretende realmente comparar os objetos ou armazená-los com hash. Substituir apenas um deles sem o outro é uma boa maneira de estragar o seu código (comparando resultados incorretos ou valores de colisão de balde insanamente altos) - e contornar isso é uma fonte de confusão e erros constantes para iniciantes (pesquise SO para ver para você) e incômodo constante para os mais experientes.Além disso, observe que, embora o C # lide com iguais e hashcode de uma maneira um pouco melhor, o próprio Eric Lippert afirma que eles cometeram quase o mesmo erro com o C # que a Sun com o Java anos antes do início do C # :
1 , é claro,
Object#hashCode
ainda pode colidir, mas é preciso um pouco de esforço para fazer isso, consulte: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 e relatórios de erros vinculados para obter detalhes; /programming/1381060/hashcode-uniqueness/1381114#1381114 aborda esse assunto mais detalhadamente.fonte
Object
design; hashability era.