Se puede decir que los métodos hashcode () e igual () son una característica importante de la completamente orientada a los objetos de Java. Facilita nuestra programación y también trae muchos peligros. En este artículo, discutiremos cómo comprender y usar correctamente estos dos métodos.
Si decide reescribir el método Equals (), debe ser claro sobre los riesgos presentados al hacerlo y asegurarse de poder escribir un método sólido igual (). Una cosa que debe tener en cuenta es que después de reescribir igual (), debe reescribir el método hashcode (). Las razones específicas se explicarán más adelante.
Primero echemos un vistazo a la descripción del método igual () en la especificación de Javase 7:
・ Es reflexivo: para cualquier valor de referencia no nulo x, x.equals(x) debe devolver true .
・ Es simétrico: para cualquier valor de referencia no nulo x e y, x.equals(y) debería devolver true si y solo si y.equals(x) devuelve true .
・ Es transitivo: para cualquier valor de referencia no nulo x, y, y z, si x.equals(y) devuelve verdadero e y.equals(z) devuelve true , entonces x.equals(z) debería devolver true.
・ Es consistente: para cualquier valor de referencia no nulo x y y , múltiples invocaciones de x.equals(y) Devuelve constantemente true o de manera false , no se modifica la información utilizada en las comparaciones iguales en los objetos.
・ Para cualquier valor de referencia no nulo x, x.equals(null) debe devolver false .
Este pasaje utiliza mucha numerología en matemáticas discretas. Déjame dar una breve explicación:
1. Reflexividad: A.Equals (a) Deben devolver verdadero.
2. Simetría: If A.Equals (b) Devuelve verdadero, entonces B.Equals (a) también necesita devolver verdadero.
3. Transmisión: si A.Equals (b) es verdadero y B.Equals (c) es verdadero, entonces A.Equals (c) también debe ser verdad. Para decirlo sin rodeos, a = b, b = c, entonces a = C.
4. Consistencia: mientras el estado de los objetos A y B no cambie, A.Equals (b) siempre debe devolverlo.
5. A.Equals (NULL) para devolver falso.
Creo que mientras las personas que no sean profesionales en matemáticas no llamen las cosas anteriores. En la aplicación real, solo necesitamos reescribir el método igual () de acuerdo con ciertos pasos. Para conveniencia de explicación, primero definimos una clase de programador (codificador):
Coder de clase {nombre de cadena privada; edad privada int; // getters and setters}Lo que queremos es que si los nombres y edades de los dos objetos programador son los mismos, entonces creemos que estos dos programadores son los mismos. En este momento, tenemos que reescribir su método igual (). Debido a que el valor predeterminado es igual () realmente determina si dos referencias apuntan al mismo objeto intrínseco, es equivalente a ==. Al reescribir, siga los siguientes tres pasos:
1. Determine si es igual a usted mismo.
if (otro == esto) devuelve verdadero;
2. Use la instancia del operador para determinar si otro es un objeto de tipo codificador.
if (! (otra instancia de codificador)) return false;
3. Compare los dominios de datos, el nombre y la edad que personaliza en la clase del codificador, y no debe perderse uno.
Codificador o = (codificador) otro; return o.name.equals (nombre) && o.age == edad;
Al ver esto, alguien puede preguntar, hay un elenco en el paso 3. Si alguien pasa un objeto de clase entera en esto igualmente, ¿lanzará una ClassCastException? Esta preocupación es en realidad redundante. Debido a que hemos hecho el juicio de instancia en el segundo paso, si otro es un objeto no codificador, o incluso otro es nulo, entonces False se devolverá directamente en este paso, de modo que el código posterior no tendrá la oportunidad de ser ejecutado.
Los tres pasos anteriores también son los pasos recomendados en <Java> Efective, que básicamente pueden garantizar que no haya ningún error.
En la especificación de Javase 7,
"Tenga en cuenta que generalmente es necesario anular el método hashcode siempre que se anule este método (igual), para mantener el contrato general para el método hashcode, que establece que los objetos iguales deben tener códigos hash iguales".
Si reescribe el método igual (), recuerde reescribir el método hashcode (). Hemos aprendido las tablas hash en los cursos de estructura de datos de informática de la universidad. El método hashcode () sirve a la tabla hash.
Cuando usamos una clase de colección que comienza con hash como hash, como hashmap y hashset, hashcode () se llamará implícitamente para crear una relación de mapeo hash. Explicaremos esto más tarde. Aquí nos centraremos primero en la escritura del método hashcode ().
<Efection Java> proporciona un método de escritura que puede evitar conflictos hash en la mayor medida, pero personalmente creo que no es necesario hacer tantos problemas para las aplicaciones generales. Si necesita almacenar decenas de miles o millones de objetos en su aplicación, debe seguir estrictamente los métodos dados en el libro. Si está escribiendo una aplicación pequeña y mediana, entonces los siguientes principios son suficientes:
Es necesario asegurarse de que todos los miembros del objeto Coder se puedan reflejar en el codo hash.
Para este ejemplo, podemos escribir esto:
@Override public int hashcode () {int resultado = 17; resultado = resultado * 31 + name.hashCode (); resultado = resultado * 31 + edad; resultado de retorno; }Donde int resultado = 17 también puede cambiarlo a 20, 50, etc. Al ver esto, de repente tenía curiosidad y quería ver cómo se implementa el método hashcode () en la clase de cadena. Consulte la documentación y sepa:
"Devuelve un código hash para esta cadena. El código hash para un objeto de cadena se calcula como
S [0]*31^(N-1) + S [1]*31^(N-2) + ... + S [N-1]
Usando intitmética int, donde s [i] es el carácter ésimo de la cadena, n es la longitud de la cadena y ^ indica exponenciación. (El valor hash de la cadena vacía es cero.) "
Calcule el código ASCII de cada carácter a la potencia N - 1 y luego agréguelo. Se puede ver que el sol es muy estricto en la implementación de hashcode. Esto puede evitar el mismo húsico en las dos cuerdas diferentes en la mayor medida.
El concepto de cubo se hace referencia en la implementación de la tabla hash de Oracle. Como se muestra en la figura a continuación:
Como se puede ver en la figura anterior, la tabla hash con cubo es aproximadamente equivalente a una combinación de una tabla hash y una lista vinculada. Es decir, se colgará una lista vinculada en cada cubo, y cada nodo de la lista vinculada se utilizará para almacenar objetos. Java usa el método hashcode () para determinar qué cubo debe ubicarse un objeto, y luego lo busca en la lista vinculada correspondiente. Idealmente, si su método hashcode () se escribe lo suficientemente robusto, entonces cada cubo tendrá solo un nodo, lo que logrará la complejidad de tiempo de nivel constante de la operación de búsqueda. Es decir, no importa en qué memoria se coloque su objeto, puedo localizar inmediatamente el área a través de hashcode () sin atravesar y buscar de principio a fin. Esta es también la aplicación principal de tablas hash.
como:
Cuando llamamos al método Put (Object O) de Hashset, primero lo localizaremos en el cubo correspondiente de acuerdo con el valor de retorno de O.HashCode (). Si no hay nodos en el balde, entonces ponga O. Si ya hay nodos, cuelgue O al final de la lista vinculada. Del mismo modo, cuando llame contiene (objeto O), Java ubicará el cubo correspondiente a través del valor de retorno de hashcode (), y luego llamará al método igual () a su vez en los nodos en la lista vinculada correspondiente para determinar si el objeto en el nodo es el objeto que desea.
Usemos un ejemplo para experimentar este proceso:
Primero creemos dos nuevos objetos codificadores:
Coder C1 = nuevo codificador ("Bruce", 10); Coder C2 = nuevo Coder ("Bruce", 10);Suponga que hemos reescribido el método Equals () de Coder sin reescribir el método hashcode ():
@Override public boolean igual (objeto otro) {System.out.println ("iguales el método invocado!"); if (otro == esto) devuelve verdadero; if (! (otra instancia de codificador)) return false; Codificador o = (codificador) otro; return o.name.equals (nombre) && o.age == edad; }Luego construimos un hashset y colocamos el objeto C1 en el conjunto:
Set <coder> set = new Hashset <Coder> (); set.add (c1);
Ejecutar de nuevo:
System.out.println (set.contains (c2));
Esperamos que el método Contiene (C2) devuelva verdadero, pero de hecho devuelve falso.
El nombre y la edad de C1 y C2 son los mismos. ¿Por qué llamo contiene (C2) y devuelve falso después de poner C1 en un hashset? Este es el hashcode () que está causando problemas. Debido a que no ha reescribido el método hashcode (), cuando hashset busca C2, lo buscará en diferentes cubos. Por ejemplo, si C1 se coloca en el cubo 05, se busca en el cubo 06 cuando busca C2, por lo que, por supuesto, no se puede encontrar. Por lo tanto, el propósito de nuestra reescritura hashcode () es que cuando A.Equals (b) devuelve verdadero, el hashcode () de A y B debería devolver el mismo valor.
¿Le pido a hashcode () que devuelva una línea numérica fija cada vez
Alguien podría reescribirlo así:
@Override public int hashcode () {return 10; }Si este es el caso, hashmap, hashset y otras clases de recolección perderán su "significado hash". En palabras de <java> efectivo>, la tabla hash degenera en una lista vinculada. Si hashcode () devuelve el mismo número cada vez, entonces todos los objetos se colocarán en el mismo cubo, y cada vez que realice una operación de búsqueda, atravesará la lista vinculada, lo que perderá por completo la función del hashing. Por lo tanto, es mejor proporcionar un hashcode robusto () como una buena idea.
Lo anterior es toda la introducción detallada de este artículo sobre la reescritura de los métodos hashcode () e igual (). Espero que sea útil para todos. Los amigos interesados pueden continuar referiéndose a otros temas relacionados en este sitio. Si hay alguna deficiencia, deje un mensaje para señalarlo. ¡Gracias amigos por su apoyo para este sitio!