A verdadeira resposta para
por que existe uma Comparatorinterface, mas não Hashere 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/ Comparatorfoi 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 Objectum 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 Objectsubclasses que realmente as implementaram tornaria essa bagunça igual (sic!) a Clonableuma ( 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 Hashtabletem 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 equalse hashCodesã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- equalsconexã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 Hashtableteriam que implementar explicitamente alguma interface (e as interfaces ainda estavam no seu início naquela época ... Comparableainda nem!) , tornando isso um impedimento para usá-lo para muitos - ou Objectteria 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 Objects são essencialmente inútil por ser redundante (tornando a.equals(b) igual a a==be a.hashCode() == b.hashCode() aproximadamente igual a a==btambém, a menos que hashCodee / ou equalsé anulado, ou você GC centenas de milhares de Objects 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 IHashableinterface. 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#hashCodeainda 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.
Personimplementar o esperadoequalse ohashCodecomportamento. 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. OObjecttipo inteiro de Java é um amálgama de responsabilidades diferentes - apenas os métodosgetClass,finalizeetoStringparecem remotamente justificáveis pelas melhores práticas de hoje.