¿Por qué tiene que reescribir el método hashCode cuando reescribe el método equals?

Sabemos que en la clase de objeto padre de nivel superior diseñada por Java, hay dos métodos especiales, son el método equals y el método hashCode. Cuando lo usamos, siempre se nos recuerda que una vez que reescribimos el método equals, debemos reescribir el método hashCode . ¿Por qué? Muchos estudiantes pensarán en esta pregunta, y hablar de ella es el propósito de este artículo.

Antes de explorar formalmente el motivo, hagamos la preparación preliminar: para comprender la conexión inextricable entre estos dos métodos, echemos un vistazo a lo que dijo el autor de la clase Object. Abra la clase Object y podemos ver Anotar de la siguiente manera.
inserte la descripción de la imagen aquí
El significado general en los comentarios del método equals en la clase Object
es: cuando reescribimos el método equals, es necesario reescribir el método hashCode, para asegurar que el contrato de "el mismo objeto debe tener el mismo valor hash " en el método hashCode no se viola

El autor de la clase Object aquí solo nos recuerda que la reescritura es necesaria. La reescritura es para mantener la definición del diseño del método hashCode, pero ¿por qué mantener la definición del diseño del método hashCode ? Seguimos mirando la definición del método hashCode con dudas.
método hashCode en la clase Object

El método hashCode es esencialmente una función hash. Esta no es mi suposición basada en el significado literal, pero el autor de la clase Object lo explicó. El autor de la clase Object escribió entre paréntesis del último párrafo del comentario: Asigne el valor de dirección del objeto a un valor hash de tipo entero . (Los estudiantes que no entiendan la definición de funciones hash pueden leer mi otro artículo: Comprender las funciones hash de una manera popular). Una comprensión firme de la definición de una función hash nos ayudará a comprender lo que sigue.

Vemos que hay una lista en el comentario del método hashCode, y hay tres comentarios en la lista. El significado general que debe entenderse en este momento es el siguiente:

1. Un objeto que llama a su método hashCode varias veces debe devolver el mismo número entero (valor hash).
2. Si se determina que dos objetos son iguales mediante el método de igualdad, se debe devolver el mismo entero.
3. Llamar al método hashCode de dos objetos con valores de dirección desiguales no requiere devolver enteros desiguales, pero requiere que los objetos con dos enteros desiguales sean objetos diferentes.

Los tres elementos enumerados anteriormente pertenecen completamente a la definición y las propiedades de las funciones hash. Así que sustituimos los tres comentarios anteriores en la definición de la función hash para ayudar a entender. Si aún no puedes entenderlo, también puedes mirar la figura a continuación:
Correspondencia entre objetos y valores hash
Podemos ver que hay dos situaciones independientes en la figura:

1. El mismo objeto necesariamente da como resultado el mismo valor hash.
2. Los diferentes valores de hash deben ser causados ​​por diferentes objetos.

Es decir, la definición escrita por el autor en el comentario del método hashCode. De hecho, el autor está implementando una función hash y escribiendo la definición de la función hash en el comentario.

De hecho, cuando vemos esto, podemos entender una cosa: el método equals y el método hashCode se usan juntos. Para cualquier objeto, ya sea que use el método equals heredado de Object o anule el método equals. Una cosa que el método hashCode realmente tiene que hacer es devolver el mismo valor hash para los mismos objetos que el método equals considera iguales .

El método equals en la clase Object distingue entre dos objetos comparando valores de dirección, es decir, usando "==". Y si reescribimos la implementación del método equals de acuerdo con los requisitos comerciales, también debemos reescribir la implementación del método hashCode al mismo tiempo. De lo contrario, el método hashCode aún devuelve el valor hash entero obtenido de acuerdo con el valor de la dirección en la clase Object.

Puede que no sea muy fácil de entender, sustituyámoslo en una clase de cadena de ejemplo específica.

En la clase String, el método equals ha sido reescrito, y el código fuente de implementación específico es el siguiente:
Implementación anulada del método equals de la clase String
A través del código fuente, podemos ver que cuando el objeto String llama al método equals para comparar otro objeto, además de determinar que dos los objetos con el mismo valor de dirección son iguales, también determina que los objetos Two String correspondientes cuyos caracteres son iguales también son iguales, incluso si los valores de dirección de los dos objetos String son diferentes (es decir, pertenecen a ambos objetos) .

Lo que podemos pensar en este momento es que el método equals en la clase String ha sido reescrito y expandido, pero si no reescribimos el método hashCode en este momento, entonces la clase String llama al método hashCode desde el padre de nivel superior clase Clase de objeto. Es decir, para dos objetos de cadena, use sus respectivos valores de dirección para asignar valores hash. Es decir, ocurrirá la siguiente situación:
Cree dos objetos de cadena con diferentes valores de dirección y el mismo valor literal
es decir, dos objetos que son considerados iguales por el método equals en la clase String tienen dos valores hash diferentes (porque sus valores de dirección son diferentes). En este punto del análisis del problema, la pregunta original "¿Por qué hay que reescribir el método hashCode al reescribir el método equals" ha terminado, y su respuesta es "Porque se debe asegurar que el método reescrito equals reconozca que el los mismos dos objetos tienen el mismo método hashCode. "Valor griego ". Al mismo tiempo, llegamos a una conclusión por cierto: " El principio de reescritura del método hashCode es garantizar que los dos objetos identificados por el método equals como iguales tengan el mismo valor hash ".

Al ver esto, mi corazón ni siquiera se siente un poco turbulento: ¿qué pasa con dos objetos String con el mismo valor literal y diferentes valores hash? ¡Y qué si no obedezco las instrucciones que me da el autor de la clase Object! Habiendo dicho eso, ninguna de las cosas mencionadas anteriormente ha hecho ninguna diferencia en mi código real hasta ahora. ¿Es ese realmente el caso? ¿No es un inconveniente o un error que dos objetos que se consideran iguales tengan hashes diferentes ? Esta es la pregunta que vamos a profundizar: ¿por qué se debe garantizar que tengan el mismo valor hash? "¿Qué papel juega el valor hash devuelto por el método hashCode en el lenguaje? ".

De hecho, en la primera mitad del artículo, al analizar los comentarios del autor de Java, omití algunas cosas que no fueron explicadas, es decir, el autor mencionó varias veces HashMap y HashTable. Entonces, a continuación, observamos con dudas el método put para almacenar datos en la clase HashMap. En realidad, llama al método putVal. La siguiente es su interceptación de fragmentos: al colocar un par clave-valor, los procesos en los cuadros 1 y 2 realizan aproximadamente
inserte la descripción de la imagen aquí
el siguientes operaciones:

No es factible probar que dos objetos son el mismo objeto solo por valores hash iguales, y se requiere prueba adicional, es decir, en el recuadro rojo No. 2 en la figura anterior, para probar que los dos objetos son los mismos. mismo objeto, requerimos (ambos Los valores hash son iguales) y (los valores de dirección de los dos son iguales o iguales se llama).

¡Aviso! Hay un problema con todo este proceso. Combinado con lo anterior, supongamos que aquí usamos un valor de tipo String como valor de clave, y esta clase String reescribe el método equals sin reescribir el método hashCode.

Luego, todavía hay dos objetos String s1 y s2 con diferentes valores de dirección y el mismo valor literal. Dado que el método hashCode no se ha reescrito de manera específica, hashCode aún obtiene los valores hash de s1 y s2 respectivamente a través de los valores de dirección Son obviamente diferentes.

El hash en el cuadro rojo No. 0 es el valor hash de la clave entrante, y la posición de la matriz obtenida mediante la paridad y la combinación con la longitud de la pestaña de la matriz subyacente del HashMap es la posición del nodo de destino final en la matriz. . Es decir, incluso si ingresamos dos s1 y s2 con exactamente el mismo valor literal, porque sus valores de dirección son diferentes, los valores hash obtenidos también son diferentes, como resultado, el nodo p detectado siempre es nulo (casilla roja número 0), es decir, se realizará la operación – creando un nuevo nodo.

Correspondiente a nuestra operación put, es equivalente a ejecutar hashMap.put("k", "v1"), hashMap.put("k": "v2"), en lugar de usar v2 para reemplazar el valor de v1, por lo que nuestro HashMap está desordenado.

Aunque aquí solo hemos llevado a cabo una prueba muy simple y unilateral, el problema se ha excavado aquí y todavía muestra que es inconveniente que el mismo objeto tenga diferentes valores hash. Es casi seguro decir que el método hashCode no solo se usa con iguales, incluso se usa con colecciones de Java . Del mismo modo, también se pueden encontrar códigos similares en HashTable, sin mencionar colecciones como HashSet.

La colección en sí es indispensable en nuestra codificación diaria, por lo que debemos reescribir obedientemente el método hashCode para evitar problemas en el código en el futuro. Afortunadamente, generalmente usamos String inmutable para la eficiencia y seguridad de las colecciones. Ha reescrito el método hashCode, y el método reescrito es un método hashCode con un hash excelente. No lo discutiré aquí debido a limitaciones de espacio. . (FIN)

Supongo que te gusta

Origin blog.csdn.net/sdujava2011/article/details/130079906
Recomendado
Clasificación