O método Equals e HashCode em Java estão em objeto; portanto, cada objeto possui esses dois métodos. Às vezes, precisamos implementar necessidades específicas e podemos ter que reescrever esses dois métodos. Hoje, apresentaremos as funções desses dois métodos.
Os métodos iguais () e hashcode () são usados para comparar na mesma classe, especialmente ao armazenar o mesmo objeto de classe no contêiner, como definido para armazenar objetos na mesma classe.
Aqui precisamos primeiro entender um problema:
Dois objetos com equals () iguais, hashcode () devem ser iguais e dois objetos com igual () não iguais, não podem provar que seu hashcode () não é igual. Em outras palavras, para dois objetos cujo método iguals () não é igual, o hashcode () pode ser igual. (Meu entendimento é causado pelos conflitos do código de hash quando gerado)
Aqui o HashCode é como o índice de cada caractere no dicionário, e iguals () é como comparar palavras diferentes com o mesmo personagem no dicionário. Assim como no dicionário, pesquisando as duas palavras "self" e "espontâneo" sob a palavra "eu" no dicionário, se igual a () é usado para determinar a igualdade das palavras, é a mesma palavra. Por exemplo, as duas palavras comparadas por iguais () são "self", então os valores obtidos pelo método hashcode () devem ser iguais neste momento; Se o método iguals () compara as palavras "self" e "espontâneo", o resultado é que você não quer esperar, mas essas duas palavras pertencem às palavras "eu" e, assim, ao procurar índices, ou seja, hashcode () é o mesmo. Se Equals () compara as palavras "self" e "eles", os resultados também são diferentes e os resultados obtidos pelo HashCode () também são diferentes no momento.
Por outro lado: o hashcode () é diferente e igual a () pode ser introduzido; hashcode () é igual, iguais () pode ser igual ou não pode ser igual. Na classe de objeto, o método hashcode () é um método local, que retorna o valor do endereço do objeto. O método iguals () na classe de objeto também compara os valores de endereço dos dois objetos. Se iguals () é igual, significa que os valores de endereço dos dois objetos também são iguais, é claro que o hashCode () é igual;
Ao mesmo tempo, o algoritmo de hash oferece alta eficiência para encontrar elementos
Se você deseja descobrir se um objeto está contido em uma coleção, como você escreve o código do programa aproximado?
Você geralmente retira cada elemento um a um para comparar com o objeto que está procurando. Quando você achar que o resultado da comparação do método igual entre um elemento e o objeto que você está procurando, pare de pesquisar e retorne informações positivas. Caso contrário, retorne informações negativas. Se houver muitos elementos em uma coleção, como 10.000 elementos e não conter o objeto que você está procurando, isso significa que seu programa precisa retirar 10.000 elementos da coleção e comparar um por um para obter uma conclusão.
Alguém inventou um algoritmo de hash para melhorar a eficiência de encontrar elementos de um conjunto. Dessa forma, o conjunto é dividido em várias áreas de armazenamento. Cada objeto pode calcular um código de hash e o código de hash pode ser agrupado (calculado usando diferentes funções de hash). Cada grupo corresponde a uma determinada área de armazenamento. De acordo com o hash de um objeto, ele pode determinar em qual área o objeto deve ser armazenado. Hashset usa um algoritmo de hash para acessar o conjunto de objetos. Ele usa internamente o método de tomar o restante de um certo número n (essa função de hash é a mais fácil) para agrupar e dividir os códigos de hash. A classe Object define um método hashcode () para retornar o código de hash de cada objeto Java. Ao procurar um objeto de uma coleção de hashset, o sistema Java chama primeiro o método hashcode () do objeto para obter a tabela de código de hash do objeto. , encontre a área de armazenamento correspondente com base no hash e, finalmente, obtenha cada elemento na área de armazenamento e compare -o com o objeto para o método igual; Dessa forma, você pode obter a conclusão sem atravessar todos os elementos da coleção. Pode -se observar que a coleção de hashset tem um bom desempenho de recuperação de objetos, mas a eficiência de armazenar objetos na coleção de hashset é relativamente baixa, porque ao adicionar um objeto à coleção de hashset, você deve primeiro calcular o código de hash do objeto e determinar o local de armazenamento do objeto na coleção baseada neste código de hash. Para garantir que os objetos de instância de uma classe possam ser armazenados normalmente no hashset, os resultados dos dois objetos de instância dessa classe também devem ser iguais quando os resultados comparados pelo método iguals () são iguais; isto é, se o resultado de obj1.equals (obj2) for verdadeiro, o resultado da seguinte expressão também deve ser verdadeira:
obj1.hashcode () == obj2.hashcode ()
Em outras palavras: quando reescrevermos o método igual de um objeto, devemos reescrever seu método HashCode. No entanto, se não reescrevermos seu método HashCode, o método HashCode no objeto sempre retorna o endereço de hash de um objeto, e esse endereço nunca será igual. Portanto, mesmo que o método igual seja reescrito neste momento, não haverá efeito específico, porque se o método HashCode não quiser esperar, ele não chamará o método igual para comparação, por isso não faz sentido.
Se o método hashcode () de uma classe não seguir os requisitos acima, quando os resultados da comparação entre os dois objetos de instância dessa classe forem iguais com o método iguals (), eles não devem ser armazenados no conjunto ao mesmo tempo. No entanto, se eles forem armazenados no conjunto de hashset, uma vez que o valor de retorno do método hashcode () é diferente (o valor de retorno do método HashCode no objeto é sempre diferente), o segundo objeto pode ser colocado em uma área diferente do primeiro objeto, de acordo com o cálculo do código de hash, para que você possa comparar o método igual ao primeiro objeto, ele pode ser armazenado, que não pode comparar o método igual ao primeiro objeto; O método hashcode () na classe de objeto não pode atender aos requisitos do objeto que está sendo armazenado no hashset, porque seu valor de retorno é calculado a partir do endereço de memória do objeto. O valor de hash retornado pelo mesmo objeto a qualquer momento durante a execução do programa é sempre inalterado. Portanto, desde que sejam dois objetos de instância diferentes, mesmo que os resultados da comparação de seu método igual sejam iguais, o valor de retorno do método de hashcode padrão é diferente.
Vamos dar uma olhada em um exemplo específico:
Objeto RectoBject: pacote com.weijia.demo; classe pública Rectobject {public int x; public int y; public retOBject (int x, int y) {this.x = x; this.y = y; } @Override public int hashCode () {final int prime = 31; int resultado = 1; Result = Prime * resultado + x; Result = Prime * resultado + y; resultado de retorno; } @Override public boolean equals (object obj) {if (this == obj) retorna true; if (obj == null) retorna false; if (getClass ()! = obj.getclass ()) retornar false; Rectobject final outro = (RectoBject) obj; if (x! = other.x) {return false; } if (y! = outro.y) {return false; } retornar true; }} Substituímos os métodos HashCode e é igual ao objeto da classe pai e vemos que nos métodos HashCode e é igual a se os valores x e y dos dois objetos retomos forem iguais, seus valores de código de hash são iguais e igual a retornar verdadeiro;
Aqui está o código de teste:
pacote com.weijia.demo; importar java.util.hashset; classe pública Demo {public static void main (string [] args) {hashset <CretObject> set = new HashSet <CectObject> (); RetObject r1 = new Rectobject (3,3); RectoBject r2 = novo RectoBject (5,5); RetObject r3 = new Rectobject (3,3); set.add (r1); set.add (r2); set.add (r3); set.add (r1); System.out.println ("size:"+set.size ()); }} Armazenamos quatro objetos no hashset e imprimimos o tamanho da coleção de conjuntos. Qual é o resultado?
Resultado em execução: Tamanho: 2
Por que é 2? Isso é muito simples, porque reescrevemos o método HashCode da classe RectoBject. Enquanto os valores de atributo X e Y do objeto RetObject forem iguais, seus valores de código de hashcode também são iguais. Então, primeiro compare os valores de código de hash. Os valores de atributo x e y dos objetos R1 e R2 não são iguais, portanto, seus códigos de hash são diferentes; portanto, o objeto R2 pode ser colocado, mas os valores de atributo X e Y do objeto R3 são os mesmos que os valores de atributo do objeto R1, de modo que o código de hash é igual. Nesse momento, comparamos o método igual de R1 e R3, porque os valores X e Y dos dois são iguais; portanto, os objetos R1 e R3 são iguais, portanto, o R3 não pode ser colocado. Além disso, adicionando um R1 no final não é adicionado; portanto, existem apenas dois objetos, R1 e R2 no conjunto.
Em seguida, comentamos o método HashCode no objeto RetObject, ou seja, não substituímos o método HashCode no objeto do objeto e executamos o código:
Resultado em execução: Tamanho: 3
Este resultado também é muito simples. Primeiro, julgue o código de hash do objeto R1 e o objeto R2. Como o método HashCode no objeto retorna o resultado da conversão do endereço de memória local do objeto, o código de hash de diferentes objetos de instância é diferente. Da mesma forma, como o código de hash de R3 e R1 também é desigual, mas R1 == R1, portanto, no conjunto final, existem apenas três objetos R1, R2 e R3, então o tamanho é 3
Em seguida, comentamos o conteúdo do método Equals no objeto RetObject, retornando diretamente false, sem comentar o método HashCode e executar o código:
Resultado em execução: Tamanho: 3
Este resultado é um pouco inesperado, vamos analisá -lo:
Primeiro, os objetos de R1 e R2 comparam o HashCode, que não são iguais, portanto, R2 é colocado no conjunto e, em seguida, observe os métodos HashCode de R3 e compare R1 e R3, que são iguais e, em seguida, comparam seus métodos iguais. Como o método igual sempre retorna false, R1 e R3 também são desiguais e não há necessidade de mencionar R3 e R2. Os códigos de hash de seus dois não são iguais, portanto, o R3 é colocado no conjunto e, em seguida, observe o R4 e compare R1 e R4, encontre que os códigos de hash são iguais. Ao comparar o método igual, porque iguais retorna falsos, R1 e R4 não são iguais, os mesmos R2 e R4 também são desiguais e R3 e R4 também são desiguais, para que o R4 possa ser colocado no conjunto, então o resultado deve ser o tamanho: 4, então por que é 3?
Neste momento, precisamos verificar o código -fonte do hashset. Aqui está o código -fonte do método Add no hashset:
/*** Adiciona o elemento especificado a esse conjunto, se ainda não estiver presente. * Mais formalmente, adiciona o elemento especificado <tt> e </tt> a esse conjunto se * este conjunto não contém nenhum elemento <Tt> e2 </tt> tal que * <Tt> (e == null? E2 == NULL: E.Equals (e2)) </tt>. * Se esse conjunto já contiver o elemento, a chamada deixa o conjunto * inalterado e retorna <tt> false </tt>. * * @param e elemento a ser adicionado a esse conjunto * @return <tt> true </tt> se esse conjunto ainda não continha o elemento * especificado */ public boolean add (e e) {retorna map.put (e, presente) == null; } Aqui podemos ver que o hashset é realmente implementado com base no hashmap. Estamos clicando no método put de hashmap, o código -fonte é o seguinte:
/*** Associa o valor especificado à chave especificada neste mapa. * Se o mapa anteriormente continha um mapeamento para a chave, o valor antigo * será substituído. * * @Param chave da chave com a qual o valor especificado deve ser associado * @param valor do valor a ser associado à chave especificada * @return o valor anterior associado à chave <tt> </tt> ou * <Tt> null </tt> se não houve mapeamento para <tt> </tt>. * (A <tt> null </tt> retorno também pode indicar que o mapa * anteriormente associado <tt> null </tt> com <tt> key </tt>.) */Public v put (k -chave, v value) {if (key == null) retornar putfornullkey (valor); int hash = hash (chave); int i = indexfor (hash, tabela.length); para (entrada <k, v> e = tabela [i]; e! = null; e = e.next) {objeto k; if (e.hash == hash && ((k = e.Key) == key || key.equals (k))) {v OldValue = e.value; E.Value = Value; E.RecordAccess (isto); retornar OldValue; }} modCount ++; addentry (hash, chave, valor, i); retornar nulo; } Vamos olhar principalmente para as condições de julgamento do IF.
Primeiro, determinamos se o código de hash é igual. Se não for igual, pule diretamente. Se for igual, compare se esses dois objetos são iguais ou o método igual a esses dois objetos. Por ser executado ou operado, desde que seja verdade, podemos explicá -lo aqui. De fato, o tamanho do conjunto acima é 3, porque o último R1 não foi colocado, e pensou -se que R1 == R1 retornou verdadeiro, para que não tenha sido colocado. Portanto, o tamanho do conjunto é 3. Se definirmos o método HashCode para sempre retornar falso, este conjunto será 4.
Por fim, vamos dar uma olhada no vazamento de memória causado pelo HashCode: veja o código:
pacote com.weijia.demo; importar java.util.hashset; classe pública Demo {public static void main (string [] args) {hashset <CretObject> set = new HashSet <CectObject> (); RetObject r1 = new Rectobject (3,3); RectoBject r2 = novo RectoBject (5,5); RetObject r3 = new Rectobject (3,3); set.add (r1); set.add (r2); set.add (r3); r3.y = 7; System.out.println ("tamanho antes da exclusão:"+set.size ()); set.remove (r3); System.out.println ("tamanho após a exclusão:"+set.size ()); }} Resultados em execução:
Tamanho antes da exclusão: 3
Tamanho excluído: 3
Rush, encontrei um problema, e foi um grande problema. Ligamos para remover para excluir o objeto R3, pensando que ele havia sido excluído, mas na verdade não foi excluído. Isso é chamado de vazamento de memória, que é um objeto não utilizado, mas ainda está na memória. Então, depois de operamos isso muitas vezes, a memória explode. Dê uma olhada no código -fonte de remover:
/*** Remove o elemento especificado deste conjunto, se estiver presente. * Mais formalmente, remove um elemento <tt> e </tt> tal que * <tt> (o == null? E == null: o.equals (e)) </tt>, * se este conjunto contiver esse elemento. Retorna <tt> true </tt> Se * esse conjunto continha o elemento (ou equivalentemente, se esse conjunto * foi alterado como resultado da chamada). (Este conjunto não conterá o elemento * assim que a chamada retornar.) * * @Param o objeto a ser removido deste conjunto, se presente * @return <tt> true </tt> se o conjunto continha o elemento especificado */ public boolean Remover (objeto o) {retornar map.rove (o) == presente; } Em seguida, observe o código -fonte do método Remover:
/*** Remove o mapeamento para a chave especificada deste mapa, se presente. * * @Param Key Chave cujo mapeamento deve ser removido do mapa * @return o valor anterior associado à chave <Tt> </tt>, ou * <tt> null </tt> se não houvesse mapeamento para <tt> chave </tt>. * (A <tt> null </tt> Retorno também pode indicar que o mapa * associado anteriormente <tt> null </tt> com <tt> key </tt>.) */Public v remover (chave do objeto) {Entry <k, v> e = removentryKey (key); return (e == null? null: e.value); } Vamos dar uma olhada no código -fonte do método RemofEntryForKey:
/** * Remove e retorna a entrada associada à chave especificada * no hashmap. Retorna nulo se o hashmap não contiver mapeamento * para esta chave. */ entrada final <k, v> removentryforkey (chave do objeto) {int hash = (key == null)? 0: Hash (chave); int i = indexfor (hash, tabela.length); Entrada <k, v> prev = tabela [i]; Entrada <k, v> e = prev; while (e! = null) {Entrada <k, v> próximo = e.next; Objeto k; if (e.hash == hash && ((k = e.key) == key || (key! = null && key.equals (k)))) {modCount ++; tamanho--; if (prev == e) tabela [i] = próximo; else prev.next = a seguir; E.RecordRemoval (this); retornar e; } prev = e; e = próximo; } retornar e; } Vemos que, ao chamar o método Remover, primeiro usaremos o valor de hashcode do objeto para encontrar o objeto e depois excluí -lo. Esse problema é porque modificamos o valor do atributo y do objeto R3. E como o método HashCode do objeto RetObject tem o valor Y participando da operação, o código de hash do objeto R3 mudou, portanto, o R3 não é encontrado no método de remoção, a exclusão falhou. Ou seja, o código de hash do R3 mudou, mas o local que ele armazena não foi atualizado e ainda está em seu local original; portanto, quando usamos seu novo hashcode para encontrá -lo, definitivamente não o encontraremos.
De fato, o método acima é muito simples de implementar: como mostrado na figura:
Uma tabela de hash linear muito simples, a função de hash usada é mod, o código -fonte é o seguinte:
/*** Retorna o índice para o código de hash h. */ static int indexfor (int h, int length) {return h & (comprimento-1); } Na verdade, essa é uma operação de mod, mas esse tipo de operação é mais eficiente que a operação %.
1,2,3,4,5 significa o resultado do mod e cada elemento corresponde a uma estrutura de lista vinculada. Portanto, se você deseja excluir uma entrada <k, v>, primeiro receberá o HashCode, para obter o nó do cabeçalho da lista vinculada e depois iterar na lista vinculada. Se o HashCode e iguais forem iguais, exclua esse elemento.
O vazamento de memória acima me diz uma mensagem: se participarmos da operação HashCode do valor do atributo do objeto, não podemos modificar seu valor de atributo ao excluí -lo, caso contrário, problemas sérios ocorrerão.
De fato, também podemos observar o método HashCode e o método igual dos tipos de objetos correspondentes aos 8 tipos de dados básicos.
Entre eles, o código de hash do tipo básico em 8 é muito simples de retornar diretamente seu tamanho numérico. O objeto String é através de um método de cálculo complexo, mas esse método de cálculo pode garantir que, se os valores dessa sequência forem iguais, seu código de hash será igual. Os 8 tipos básicos de métodos iguais devem comparar diretamente os valores numéricos, e o tipo de string é igual ao método compara os valores das cadeias.
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.