O contrato de equals/hashCode
Por que ambos precisam concordar.
O contrato de equals/hashCode é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 2 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.
Dois métodos, um contrato
Todo objeto Java herda equals() e hashCode() de Object.
Quando você sobrescreve um deles, quase sempre precisa sobrescrever o outro. Eles formam um contrato vinculante do qual as coleções baseadas em hash dependem.
public class Main {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
System.out.println(a.equals(b));
System.out.println(a.hashCode() == b.hashCode());
}
}A regra principal
O contrato estabelece:
- Se
a.equals(b)for verdadeiro, entãoa.hashCode()deve ser igual ab.hashCode(). - Se os códigos hash forem diferentes, os objetos certamente serão diferentes.
Objetos iguais devem ter códigos hash iguais. O inverso não é obrigatório.
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hel" + "lo";
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode() == s2.hashCode());
}
}Objetos diferentes podem compartilhar um hash
É permitido que dois objetos diferentes compartilhem um código hash. Isso é apenas uma colisão, e o mapa a trata com equals().
Portanto, a única situação proibida é: objetos iguais com códigos hash diferentes.
public class Main {
public static void main(String[] args) {
// Equal value, unequal objects, same hash is fine
System.out.println("FB".hashCode() == "Ea".hashCode());
System.out.println("FB".equals("Ea"));
}
}Uma classe que sobrescreve apenas equals
Esta é uma classe defeituosa. Ela sobrescreve equals(), mas não hashCode().
Agora, dois pontos logicamente iguais têm códigos hash diferentes herdados de Object, violando o contrato.
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
// BUG: no hashCode override
}
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println("equals: " + a.equals(b));
System.out.println("same hash: " + (a.hashCode() == b.hashCode()));
}
}O erro em um HashSet
A classe defeituosa falha em um HashSet. O conjunto procura no compartimento escolhido pelo hash incorreto e nunca encontra o elemento igual.
O resultado são duplicatas que deveriam ter sido rejeitadas.
import java.util.HashSet;
import java.util.Set;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}Corrigindo o problema
Sobrescreva ambos os métodos e derive-os dos mesmos campos. Assim, objetos iguais compartilham um hash e vão para o mesmo compartimento.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}equals deve ser reflexivo
Um equals() correto obedece a quatro propriedades. A primeira é a reflexividade: x.equals(x) deve ser verdadeiro.
Isso parece óbvio, mas uma lógica personalizada pode violar essa regra se você esquecer de tratar o caso de uma mesma referência.
public class Main {
public static void main(String[] args) {
String x = "java";
System.out.println(x.equals(x));
}
}Simétrica e transitiva
Mais duas regras:
- Simétrica: se
a.equals(b), entãob.equals(a). - Transitiva: se
a.equals(b)eb.equals(c), entãoa.equals(c).
Misturar tipos em uma hierarquia de classes frequentemente quebra a simetria; por isso, use compare com cuidado ao verificar as classes.
public class Main {
public static void main(String[] args) {
String a = "x", b = "x", c = "x";
boolean symmetric = a.equals(b) == b.equals(a);
boolean transitive = a.equals(b) && b.equals(c) && a.equals(c);
System.out.println("symmetric: " + symmetric);
System.out.println("transitive: " + transitive);
}
}Consistente
A quarta regra é a consistência: chamadas repetidas retornam o mesmo resultado enquanto os objetos não mudarem.
É por isso que você deve basear equals e hashCode em campos imutáveis. Alterar um campo usado em hashCode após a inserção corrompe o mapa.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
// Immutable record gives consistent equals/hashCode automatically
record Id(int value) {}
Id id = new Id(7);
System.out.println(Objects.equals(id, new Id(7)));
System.out.println(id.hashCode() == new Id(7).hashCode());
}
}Os registros fazem isso por você
Um record Java gera automaticamente equals() e hashCode() a partir de todos os seus componentes.
Para dados semelhantes a valores, prefira registros. O contrato é satisfeito automaticamente e permanece sincronizado.
import java.util.HashSet;
import java.util.Set;
public class Main {
record Point(int x, int y) {}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}A armadilha da chave mutável
Se você alterar um campo usado em hashCode depois de inserir o objeto em um mapa, não poderá mais encontrá-lo.
O mapa procura no compartimento antigo; agora o objeto produz hash para um novo compartimento. A entrada se torna um fantasma perdido.
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class Main {
static class Box {
int id;
Box(int id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof Box b && b.id == id;
}
@Override public int hashCode() { return Objects.hash(id); }
}
public static void main(String[] args) {
Map<Box, String> m = new HashMap<>();
Box key = new Box(1);
m.put(key, "value");
key.id = 99; // mutated after insertion
System.out.println(m.get(key));
}
}Verificação rápida
Teste sua compreensão do contrato.
Recapitulação
Você aprendeu o contrato de equals/hashCode:
- Objetos iguais devem ter códigos hash iguais.
- equals deve ser reflexivo, simétrico, transitivo e consistente.
- Baseie os dois métodos nos mesmos campos imutáveis.
- Os registros geram implementações corretas automaticamente.
A seguir, você escreverá seu próprio hashCode correto manualmente.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
}
}Perguntas Frequentes
A aula “O contrato de equals/hashCode” é grátis?
Sim — o texto completo de “O contrato de equals/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 “O contrato de equals/hashCode”?
Por que ambos precisam concordar. 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 2 de 4.
Quanto tempo leva a aula “O contrato de equals/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