Desafio Java
Igualdade com herança e campo mutável
Enunciado
Crie uma hierarquia com uma classe base Animal e uma subclasse Cachorro. A classe Animal tem um campo nome (String) e a subclasse adiciona raca (String). Implemente equals e hashCode de forma que um Cachorro só seja igual a outro Cachorro com mesmo nome e raça, e nunca igual a um Animal que não seja Cachorro. Além disso, crie um HashSet de Animal e demonstre que a igualdade funciona corretamente. Depois, torne o campo nome mutável e mostre o problema de usá-lo como chave em um HashMap após a mutação.
Requisitos
Animaldeve terequalsehashCodebaseados emnome.Cachorrodeve sobrescreverequalsehashCodeincluindoracae usandogetClass()para evitar igualdade entre subclasses diferentes.- O programa deve mostrar que um
Cachorronão é igual a umAnimalgenérico com mesmo nome. - O programa deve demonstrar que mutar um campo usado no
hashCodequebra a busca emHashMap.
Código inicial
import java.util.*;
class Animal {
protected String nome;
Animal(String nome) {
this.nome = nome;
}
// TODO: equals e hashCode
}
class Cachorro extends Animal {
private String raca;
Cachorro(String nome, String raca) {
super(nome);
this.raca = raca;
}
// TODO: equals e hashCode
}
public class Main {
public static void main(String[] args) {
// Testes
}
}
Saída esperada
Cachorro igual a Animal? false
Cachorro igual a Cachorro? true
Tamanho do HashSet: 1
Após mutação, valor recuperado: null
Ver dica
Use getClass() != o.getClass() para garantir que apenas objetos da mesma classe sejam iguais. Para o problema da mutação, insira a chave no mapa, modifique o campo e tente recuperar.
Mostrar solução
import java.util.*;
class Animal {
protected String nome;
Animal(String nome) {
this.nome = nome;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Animal animal = (Animal) o;
return Objects.equals(nome, animal.nome);
}
@Override
public int hashCode() {
return Objects.hash(nome);
}
}
class Cachorro extends Animal {
private String raca;
Cachorro(String nome, String raca) {
super(nome);
this.raca = raca;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Cachorro cachorro = (Cachorro) o;
return Objects.equals(raca, cachorro.raca);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), raca);
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("Rex");
Cachorro c1 = new Cachorro("Rex", "Labrador");
Cachorro c2 = new Cachorro("Rex", "Labrador");
System.out.println("Cachorro igual a Animal? " + c1.equals(animal));
System.out.println("Cachorro igual a Cachorro? " + c1.equals(c2));
Set<Animal> set = new HashSet<>();
set.add(c1);
set.add(c2);
System.out.println("Tamanho do HashSet: " + set.size());
// Demonstração do problema com campo mutável
Map<Animal, String> mapa = new HashMap<>();
Animal chave = new Animal("Toby");
mapa.put(chave, "Valor");
chave.nome = "NovoNome"; // mutação
System.out.println("Após mutação, valor recuperado: " + mapa.get(chave));
}
}
Passo a passo
AnimaldefineequalsehashCodebaseados apenas emnome.Cachorrosobrescreveequalschamandosuper.equals(o)para compararnomee depois compararaca.- Usamos
getClass() != o.getClass()em ambos para garantir que umCachorronão seja igual a umAnimalgenérico, mesmo com mesmo nome. hashCodedeCachorrocombina o hash da superclasse com o hash deraca.- No
main, criamos umAnimale doisCachorroiguais. OHashSetarmazena apenas umCachorroporqueequalsehashCodesão consistentes. - Depois, inserimos um
Animalem umHashMap, mutamos seunomee tentamos recuperar. Ogetretornanullporque o hash mudou e o mapa não encontra mais a chave no balde original.
Por que funciona
A implementação segue o contrato: objetos iguais têm o mesmo hash, e a igualdade entre classes diferentes é evitada com getClass(). O HashSet funciona corretamente. Já o exemplo com mutação mostra que alterar um campo usado no hashCode quebra a busca, pois o objeto passa a ter um hash diferente do que foi usado na inserção.
Erros comuns
- Usar
instanceofem vez degetClass():animal instanceof Cachorropode permitir igualdade assimétrica seCachorrotambém sobrescreverequals. UsegetClass()para garantir simetria. - Esquecer de chamar
super.equals: emCachorro, se não chamarsuper.equals(o), a comparação denomenão é feita. - Mutar campos usados em
hashCode: como visto, isso quebra oHashMap. Evite usar campos mutáveis como chave ou torne a classe imutável.
Outra forma de resolver
Usar record para classes imutáveis, mas herança não é permitida em records. Para hierarquias, pode-se usar composição em vez de herança, evitando os problemas de igualdade entre subclasses.
Saída esperada
Cachorro igual a Animal? false
Cachorro igual a Cachorro? true
Tamanho do HashSet: 1
Após mutação, valor recuperado: null