JavaSE: ¿por qué debe reescribir hashCode cuando reescribir es igual?

Directorio de artículos:

1. Llevar a un tema

2. Sobre el método de igualdad

3. Sobre el método hashCode

4. ¿Por qué reescribir estos dos métodos juntos?

5. Resumen


1. Llevar a un tema

El método equals y el método hashCode son los dos métodos básicos de la clase Object y trabajan juntos para determinar si dos objetos son iguales. ¿Por qué está diseñado de esta manera? La razón radica en la palabra "rendimiento".

Después de usar HashMap, sabemos que después del cálculo de hash, podemos ubicar directamente la ubicación donde se almacena un valor.Entonces, imagine, si ahora desea consultar si un valor está en la colección. Si el elemento (ubicación de almacenamiento) no se localiza directamente por el método hash, entonces la consulta y la comparación solo se pueden realizar una por una según el orden de la colección, y la eficiencia de esta comparación secuencial es obviamente menor que la de la método de posicionamiento hash. Este es el valor de la existencia de hash y hashCode.

 

Cuando comparamos si dos objetos son iguales, primero podemos usar hashCode para comparar, si el resultado de la comparación es verdadero, luego podemos usar iguales para confirmar si los dos objetos son iguales nuevamente, si el resultado de la comparación es verdadero, entonces los dos Objetos son iguales, de lo contrario los dos objetos se consideran desiguales. Esto mejora enormemente la eficiencia de la comparación de objetos, razón por la cual el diseño de Java usa hashCode y equivale a sinergia para confirmar si dos objetos son iguales.

Entonces, ¿por qué no usar hashCode para determinar si dos objetos son iguales?

Esto se debe a que si dos objetos son iguales, el código hash también debe ser el mismo; si dos objetos tienen el mismo valor de código hash, no necesariamente son iguales. Por lo tanto, el uso de hashCode puede desempeñar un papel en la determinación rápida de si los objetos son iguales por primera vez.

Pero incluso si conoce los conocimientos básicos anteriores, aún no puede resolver el problema de este artículo, es decir: ¿por qué tiene que reescribir hashCode cuando reescribir es igual? Para comprender la causa raíz de este problema, debemos comenzar con estos dos métodos.


2. Sobre el método de igualdad

El método equals en la clase Object se usa para verificar si un objeto es igual a otro objeto. En la clase Object, este método determinará si dos objetos tienen la misma referencia. Si dos objetos tienen la misma referencia, deben ser iguales.

El código fuente de implementación del método equals es el siguiente:

public boolean equals(Object obj) {
    return (this == obj);
}

Del código fuente anterior y la definición de iguales, podemos ver que en la mayoría de los casos, ¡el juicio de iguales no tiene sentido! Por ejemplo, usar iguales en Objeto para comparar la igualdad de dos objetos personalizados no tiene ningún sentido (porque el resultado es falso independientemente de si los objetos son iguales).

Esto se ilustra con el siguiente ejemplo:

/**
 *
 */
class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

public class EqualsMethodTest {
    public static void main(String[] args) {
        User u1 = new User("张起灵", 18);
        User u2 = new User("张起灵", 18);
        System.out.println(u1.equals(u2));
    }
}

Por lo tanto , en circunstancias normales, si queremos juzgar si dos objetos son iguales, debemos anular el método de igualdad , por lo que anulamos el método de igualdad.

Al igual que las clases contenedoras de 8 tipos de datos, String, estas clases han reescrito el método equals.


3. Sobre el método hashCode

hashCode traducido al chino es un código hash, que es un valor entero derivado del objeto, y este valor es cualquier número entero, incluidos números positivos o negativos.

Es importante tener en cuenta que los códigos hash son irregulares. Si x e y son dos objetos diferentes, x.hashCode() e y.hashCode() básicamente no son lo mismo (puede serlo), pero si a y b son iguales, entonces a.hashCode() debe ser igual a b .código hash().

El código fuente de hashCode en Object es el siguiente:

public native int hashCode();

Como se puede ver en el código fuente anterior, hashCode en Object llama a un método local (nativo) y devuelve un número entero de tipo Int. Por supuesto, este número entero puede ser positivo o negativo.

Veamos un caso: sin anular el método hashCode, si dos objetos son iguales, ¿sus hashCodes son iguales?

/**
 *
 */
class People {
    private String name;
    private Integer age;

    public People(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

public class HashCodeMethodTest {
    public static void main(String[] args) {
        People p1 = new People("张起灵", 18);
        People p2 = new People("张起灵", 18);
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
    }
}

Los resultados anteriores son obviamente diferentes, entonces, ¿no contradice esto lo que dijimos anteriormente?

Veamos uno más: después de reescribir hashCode, dos objetos son iguales, ¿sus hashCodes son iguales? (Tome la clase String como ejemplo)

package com.interview;

/**
 *
 */
public class HashCodeMethodTest {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }
}

Pero diferentes objetos pueden tener el mismo código hash. Los ejemplos son los siguientes:

package com.interview;

/**
 *
 */
public class HashCodeMethodTest {
    public static void main(String[] args) {
        String str1 = "Aa";
        String str2 = "BB";
        System.out.println(str1.hashCode());
        System.out.println(str2.hashCode());
    }
}


4. ¿Por qué reescribir estos dos métodos juntos?

A continuación, vuelva al tema de este artículo, reescribir es igual a ¿Por qué debe reescribir hashCode?

Para explicar esto, necesitamos comenzar con el siguiente ejemplo.

La colección Set se utiliza para guardar diferentes objetos, Set fusionará el mismo objeto y finalmente dejará datos únicos.

import java.util.HashSet;
import java.util.Set;

/**
 *
 */
public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("Java");
        set.add("Java");
        set.add("SpringBoot");
        set.add("SpringBoot");
        set.add("SpringBoot");
        set.add("Redis");
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

Se puede ver a partir de los resultados anteriores que los datos duplicados han sido "fusionados" por la colección Set, que también es la característica más importante de la colección Set: la deduplicación.

En el caso anterior, el tipo genérico de la colección establecida es String. Esta clase ha reescrito equals y hashCode. Luego, modificaremos el tipo genérico a una clase personalizada, y en esta clase personalizada, solo anularemos el método equals.

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 *
 */
class Student {
    private String name;
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }

//    @Override
//    public int hashCode() {
//        return Objects.hash(name, age);
//    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("张起灵", 18));
        set.add(new Student("张起灵", 18));
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

Como se puede ver en el código anterior y en la imagen anterior, incluso si los dos objetos son iguales, la colección Set no deduplica ni fusiona los dos. Ese es el problema de anular el método equals, pero no el método hashCode.

Para resolver los problemas anteriores, es natural reescribir hashCode cuando se reescribe igual. ! !

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 *
 */
class Student {
    private String name;
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name) && Objects.equals(age, student.age);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class EqualsHashCodeTest {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        set.add(new Student("张起灵", 18));
        set.add(new Student("张起灵", 18));
        System.out.println("set集合的长度为:" + set.size());
        set.forEach(System.out::println);
    }
}

A partir de los resultados anteriores, podemos ver que cuando reescribimos los dos métodos juntos, el milagro volvió a ocurrir y la colección Set volvió a la normalidad. ¿A qué se debe esto?

El motivo del problema anterior es que si solo se anula el método equals, entonces, de manera predeterminada, cuando Set realiza la operación de deduplicación, primero determinará si el código hash de los dos objetos es el mismo. El método no se anula, ejecutará directamente el método hashCode en Object, y el método hashCode en Object compara dos objetos con diferentes direcciones de referencia (nuevo dos veces Student, luego estos dos objetos apuntan naturalmente a referencias diferentes), por lo que el resultado es falso, Entonces no es necesario ejecutar el método equals y el resultado devuelto directamente es falso: los dos objetos no son iguales, por lo que se insertan dos objetos idénticos en la colección Set.

Sin embargo, si el método hashCode también se anula cuando se anula el método equals, el método hashCode anulado se ejecutará cuando se realice el juicio. En este momento, se realiza la comparación para ver si el hashCode de todos los atributos de los dos objetos es el mismo, por lo que se llama al hashCode. El resultado devuelto es verdadero, luego llame al método equals y descubra que los dos objetos son realmente iguales, por lo que devuelve verdadero, por lo que la colección Set no almacenará dos datos idénticos, por lo que la ejecución de todo el programa es normal.


5. Resumen

Los dos métodos, hashCode y equals, se utilizan para determinar en colaboración si dos objetos son iguales. El motivo de utilizar este método es mejorar la velocidad de inserción y consulta del programa. Si no reescribe hashCode al reescribir es igual, provocará excepciones en la ejecución del programa en algunos escenarios, como cuando dos objetos personalizados iguales se almacenan en la colección Set. Para garantizar la ejecución normal del programa, necesitamos reescribir el método hashCode cuando reescribimos equals.

Supongo que te gusta

Origin blog.csdn.net/weixin_43823808/article/details/124035897
Recomendado
Clasificación