A verdadeira resposta para
por que existe uma Comparator
interface, mas não Hasher
e Equator
?
é, citação cortesia de Josh Bloch :
As APIs Java originais foram feitas muito rapidamente, dentro de um prazo apertado, para atender a uma janela de fechamento do mercado. A equipe original do Java fez um trabalho incrível, mas nem todas as APIs são perfeitas.
O problema reside unicamente na história do Java, como com outros assuntos semelhantes, por exemplo, .clone()
vs Cloneable
.
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:
- O Java, desde o início até os dias atuais, era orgulhosamente compatível com versões anteriores, exigindo que as APIs herdadas ainda fossem suportadas nas versões mais recentes,
- como tal, quase todos os construtos de idiomas introduzidos no JDK 1.0 sobreviveram até os dias atuais,
Hashtable
, .hashCode()
& .equals()
foram implementados no JDK 1.0, ( Hashtable )
Comparable
/ Comparator
foi introduzido no JDK 1.2 ( comparável ),
Agora, segue:
- era praticamente impossível e sem sentido adaptar
.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 cada Object
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 as Object
subclasses que realmente as implementaram tornaria essa bagunça igual (sic!) a Clonable
uma ( Bloch discute por que Cloneable é péssimo , também discutido em, por exemplo, EJ 2nd e muitos outros lugares, incluindo SO),
- eles apenas os deixaram lá para a geração futura ter uma fonte constante de WTFs.
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:
Os métodos equals
e hashCode
são declarados para o benefício de hashtables, como java.util.Hashtable
(§21.7). O método equals define uma noção de igualdade de objeto, que se baseia na comparação de valor, não de referência.
(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 direta Hashtable
- 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 os Object
envolvidos Hashtable
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 - ou Object
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 um equals()
método distinto - portanto, era óbvio que equals()
ele também deveria ser colocado Object
.
Desde as padrão implementações destes métodos sobre válida a
& b
Object
s são essencialmente inútil por ser redundante (tornando a.equals(b)
igual a a==b
e a.hashCode() == b.hashCode()
aproximadamente igual a a==b
também, a menos que hashCode
e / ou equals
é anulado, ou você GC centenas de milhares de Object
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 # :
Mas por que deveria ser o caso de todo objeto ser capaz de fazer um hash próprio para inserção em uma tabela de hash? Parece uma coisa estranha exigir que todos os objetos possam fazer. Acho que se estivéssemos redesenhando o sistema de tipos do zero hoje, o hash pode ser feito de maneira diferente, talvez com uma IHashable
interface. Porém, quando o sistema de tipos CLR foi projetado, não havia tipos genéricos e, portanto, uma tabela de hash de uso geral precisava ser capaz de armazenar qualquer objeto.
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.
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.