0Pricing
Java Academy · Aula

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ão a.hashCode() deve ser igual a b.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ão b.equals(a).
  • Transitiva: se a.equals(b) e b.equals(c), então a.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

  1. Como HashMap funciona
  2. O contrato de equals/hashCode
  3. Implementando hashCode
  4. Transformação em árvore e desempenho
← Voltar para Java Academy