Implementando hashCode
Escreva funções de hash corretas.
Implementando hashCode é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Java Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Java Academy inclui 4 aulas no total.
Objetivos de um hashCode bom
Um hashCode() bom deve:
- Retornar o mesmo valor para objetos iguais (o contrato).
- Distribuir objetos diferentes entre muitos valores diferentes.
- Ser barato de calcular.
Um hashCode ruim que retorna uma constante ainda satisfaz o contrato, mas transforma o mapa em uma lista encadeada 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 o caso comum
A abordagem correta mais simples é Objects.hash(field1, field2, ...).
Ela trata valores nulos e combina os campos usando um algoritmo padrão. Use os mesmos campos que você compara em 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());
}
}O multiplicador clássico 31
Para um hash escrito manualmente, o padrão usa a multiplicação do resultado acumulado por 31 e adiciona o hash de cada campo.
31 é um número primo ímpar, e 31 * x é equivalente a (x << 5) - x, portanto a JVM pode otimizá-lo.
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());
}
}Calculando hashes de tipos primitivos
Cada tipo primitivo tem uma forma recomendada de gerar seu hash:
int: use o próprio valor.long:(int)(value ^ (value >>> 32)).boolean: 1 ou 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));
}
}Calculando hashes de arrays
Não chame hashCode() diretamente em um array; ele usa a identidade, não o conteúdo.
Use Arrays.hashCode(arr) para um array simples ou Arrays.deepHashCode(arr) para arrays aninhados.
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)));
}
}Mantenha equals e hashCode sincronizados
Os campos usados em hashCode() devem ser um subconjunto dos campos usados em equals() (idealmente, exatamente os mesmos).
Se equals comparar mais campos do que hashCode, dois objetos iguais ainda compartilharão um hash. Isso é permitido. Porém, se hashCode usar um campo ignorado por equals, você violará o 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());
}
}Armazenando o hash em cache
Para objetos imutáveis cujo cálculo de hash é caro, você pode armazenar o resultado em cache em um campo.
String faz exatamente isso internamente. Faça isso apenas quando o objeto for realmente imutável, para que o valor armazenado em cache nunca fique desatualizado.
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());
}
}A distribuição é importante
Um hashCode bem distribuído espalha as chaves uniformemente pelos compartimentos. Vamos contar os códigos hash distintos de um conjunto de objetos.
Quanto mais valores distintos houver, menos colisões ocorrerão e mais rápido será o 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());
}
}Exemplo de distribuição ruim
Somar campos sem multiplicar produz colisões: (1,2) e (2,1) produzem o hash 3.
O multiplicador 31 desfaz essa simetria, pois a ordem passa 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));
}
}Prefira registros para tipos de valor
Para estruturas que apenas carregam dados, um record gera automaticamente um hashCode correto e bem distribuído.
Escreva hashCode manualmente apenas quando precisar de semântica personalizada ou não puder usar um registro.
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());
}
}Reunindo tudo
Uma classe de valor completa: campos imutáveis, equals e hashCode baseados nos mesmos campos e um toString limpo.
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());
}
}Verificação rápida
Teste seus conhecimentos sobre hashCode.
Recapitulação
Você aprendeu a implementar hashCode corretamente:
- Use
Objects.hash(...)no caso comum. - Use o padrão do multiplicador 31 para calcular hashes manualmente.
- Calcule o hash de arrays com
Arrays.hashCode, não com o método padrão. - Mantenha os campos de hashCode sincronizados com equals e prefira registros.
A seguir, veja como o Java 8+ transforma compartimentos sobrecarregados em árvores.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("hashCode recap done: " + Objects.hash("done"));
}
}Perguntas Frequentes
A aula “Implementando hashCode” é grátis?
Sim — o texto completo de “Implementando hashCode” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Java Academy, atualize para CoddyKit PRO. O curso de Java Academy inclui 4 aulas no total.
O que vou aprender em “Implementando hashCode”?
Escreva funções de hash corretas. Você pratica Java Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Java Academy?
Nenhuma experiência prévia é necessária. Java Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “Implementando hashCode”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Java Academy?
Sim. Cada aula de Java Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Como HashMap funciona
- O contrato de equals/hashCode
- Implementando hashCode
- Transformação em árvore e desempenho