0Pricing
Java Academy · Lección

Implementación de hashCode

Escriba funciones hash correctas

Implementación de hashCode es una lección gratuita de Java Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Java Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Java Academy incluye 4 lecciones en total.

Objetivos de un buen hashCode

Un buen hashCode() debe:

  • Devolver el mismo valor para objetos iguales (el contrato).
  • Distribuir los objetos distintos entre muchos valores diferentes.
  • Ser barato de calcular.

Un hashCode deficiente que devuelve siempre una constante sigue cumpliendo el contrato, pero convierte el mapa en una lista enlazada lenta.

public class Main {
    public static void main(String[] args) {
        // Legal but terrible: every object collides
        System.out.println("constant hashCode is legal but kills performance");
    }
}

Objects.hash para el caso habitual

El enfoque correcto más sencillo es Objects.hash(field1, field2, ...).

Gestiona los valores null y combina los campos mediante un algoritmo estándar. Utilice los mismos campos que compara en equals.

import java.util.Objects;

public class Main {
    static class User {
        final String name; final int age;
        User(String name, int age) { this.name = name; this.age = age; }
        @Override public int hashCode() { return Objects.hash(name, age); }
    }
    public static void main(String[] args) {
        User a = new User("Ada", 36);
        User b = new User("Ada", 36);
        System.out.println(a.hashCode() == b.hashCode());
    }
}

El multiplicador clásico 31

Para escribir un hash a mano, el patrón estándar multiplica el resultado acumulado por 31 y suma el hash de cada campo.

31 es un número primo impar, y 31 * x equivale a (x << 5) - x, por lo que la JVM puede optimizarlo.

public class Main {
    static class User {
        final String name; final int age;
        User(String name, int age) { this.name = name; this.age = age; }
        @Override public int hashCode() {
            int result = 17;
            result = 31 * result + (name == null ? 0 : name.hashCode());
            result = 31 * result + age;
            return result;
        }
    }
    public static void main(String[] args) {
        System.out.println(new User("Ada", 36).hashCode());
    }
}

Calcular el hash de tipos primitivos

Cada tipo primitivo tiene una forma recomendada de calcular el hash:

  • int: utilizar el propio valor.
  • long: (int)(value ^ (value >>> 32)).
  • boolean: 1 o 0.
  • double: Double.hashCode(value).
public class Main {
    public static void main(String[] args) {
        long id = 4_000_000_000L;
        int longHash = (int) (id ^ (id >>> 32));
        System.out.println("long hash: " + longHash);
        System.out.println("double hash: " + Double.hashCode(3.14));
        System.out.println("bool hash: " + Boolean.hashCode(true));
    }
}

Calcular el hash de arrays

No invoque hashCode() directamente en un array; utiliza la identidad, no el contenido.

Utilice Arrays.hashCode(arr) para un array plano o Arrays.deepHashCode(arr) para arrays anidados.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3};
        int[] b = {1, 2, 3};
        System.out.println("identity equal: " + (a.hashCode() == b.hashCode()));
        System.out.println("content equal: " + (Arrays.hashCode(a) == Arrays.hashCode(b)));
    }
}

Mantenga equals y hashCode sincronizados

Los campos utilizados en hashCode() deben ser un subconjunto de los campos utilizados en equals() (idealmente, exactamente los mismos).

Si equals compara más campos que hashCode, dos objetos iguales seguirán compartiendo un hash. Eso está permitido. Pero si hashCode utiliza un campo que equals ignora, incumple el contrato.

import java.util.Objects;

public class Main {
    static class Coord {
        final int x, y;
        Coord(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            return o instanceof Coord c && c.x == x && c.y == y;
        }
        @Override public int hashCode() { return Objects.hash(x, y); }
    }
    public static void main(String[] args) {
        Coord a = new Coord(3, 4), b = new Coord(3, 4);
        System.out.println(a.equals(b) && a.hashCode() == b.hashCode());
    }
}

Almacenar en caché el hash

En objetos inmutables cuyo cálculo de hash es costoso, puede almacenar el resultado en caché en un campo.

String hace exactamente esto internamente. Hágalo solo cuando el objeto sea realmente inmutable, para que el valor almacenado nunca quede obsoleto.

import java.util.Objects;

public class Main {
    static final class Key {
        final String a, b;
        private int hash; // 0 until computed
        Key(String a, String b) { this.a = a; this.b = b; }
        @Override public int hashCode() {
            int h = hash;
            if (h == 0) { h = Objects.hash(a, b); hash = h; }
            return h;
        }
    }
    public static void main(String[] args) {
        Key k = new Key("x", "y");
        System.out.println(k.hashCode());
        System.out.println(k.hashCode());
    }
}

La distribución es importante

Un hashCode bien distribuido reparte las claves uniformemente entre los buckets. Veamos cuántos códigos hash distintos tiene un conjunto de objetos.

Cuantos más valores distintos haya, menos colisiones se producirán y más rápido será el mapa.

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

public class Main {
    record Pair(int a, int b) {}
    public static void main(String[] args) {
        Set<Integer> hashes = new HashSet<>();
        for (int i = 0; i < 100; i++) {
            hashes.add(Objects.hash(i, i * 7));
        }
        System.out.println("distinct hashes: " + hashes.size());
    }
}

Un ejemplo de mala distribución

Sumar los campos sin multiplicar produce colisiones: (1,2) y (2,1) generan un hash de 3.

El multiplicador 31 rompe esta simetría porque el orden pasa a importar.

public class Main {
    static int badHash(int a, int b) { return a + b; }
    static int goodHash(int a, int b) { return 31 * a + b; }
    public static void main(String[] args) {
        System.out.println("bad (1,2): " + badHash(1, 2) + ", (2,1): " + badHash(2, 1));
        System.out.println("good (1,2): " + goodHash(1, 2) + ", (2,1): " + goodHash(2, 1));
    }
}

Prefiera los records para los tipos de valor

Para simples contenedores de datos, un record genera automáticamente un hashCode correcto y bien distribuido.

Escriba hashCode a mano solo cuando necesite una semántica personalizada o no pueda utilizar un record.

public class Main {
    record Money(long cents, String currency) {}
    public static void main(String[] args) {
        Money a = new Money(1099, "USD");
        Money b = new Money(1099, "USD");
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() == b.hashCode());
    }
}

Unir todas las piezas

Una clase de valores completa: campos inmutables, equals y hashCode basados en los mismos campos, y un toString claro.

import java.util.Objects;

public class Main {
    static final class Version {
        final int major, minor, patch;
        Version(int major, int minor, int patch) {
            this.major = major; this.minor = minor; this.patch = patch;
        }
        @Override public boolean equals(Object o) {
            return o instanceof Version v && v.major == major && v.minor == minor && v.patch == patch;
        }
        @Override public int hashCode() { return Objects.hash(major, minor, patch); }
        @Override public String toString() { return major + "." + minor + "." + patch; }
    }
    public static void main(String[] args) {
        Version v = new Version(2, 1, 0);
        System.out.println(v + " hash=" + v.hashCode());
    }
}

Comprobación rápida

Compruebe sus conocimientos sobre hashCode.

Repaso

Ha aprendido a implementar hashCode correctamente:

  • Utilice Objects.hash(...) para el caso habitual.
  • El patrón del multiplicador 31 para calcular hashes a mano.
  • Calcule el hash de los arrays con Arrays.hashCode, no con el método predeterminado.
  • Mantenga sincronizados los campos de hashCode con equals y prefiera los records.

A continuación, verá cómo Java 8+ convierte en árboles los buckets saturados.

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        System.out.println("hashCode recap done: " + Objects.hash("done"));
    }
}

Preguntas frecuentes

¿La lección «Implementación de hashCode» es gratis?

Sí — el texto completo de «Implementación de hashCode» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Java Academy, actualiza a CoddyKit PRO. El curso de Java Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Implementación de hashCode»?

Escriba funciones hash correctas Practicas Java Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Java Academy?

No se requiere experiencia previa. Java Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Implementación de hashCode»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Java Academy?

Sí. Cada lección de Java Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Cómo funciona HashMap
  2. El contrato de equals/hashCode
  3. Implementación de hashCode
  4. Treeification y rendimiento
← Volver a Java Academy