Desafio Java
Record vs Classe: comparando imutabilidade e igualdade
Enunciado
Implemente duas versões de uma entidade Coordenada: uma como record e outra como classe tradicional. Ambas devem ter x e y (int), com validação que rejeita valores negativos. No main, crie instâncias de ambas, compare-as com equals e imprima os resultados. Mostre que o record é imutável (tentativa de modificação gera erro de compilação) e que a classe pode ser mutável se fornecer setters.
Requisitos
- Defina um record
CoordenadaRecordcom validação no construtor compacto. - Defina uma classe
CoordenadaClassecom campos privados, construtor com validação, getters,equals,hashCodeetoString. - No
main, crie instâncias de ambos, compare-as comequalse imprima os resultados. - Comente no código que a classe poderia ter setters, mas o record não permite modificação após a criação.
Código inicial
public class Main {
// Defina o record CoordenadaRecord
// Defina a classe CoordenadaClasse
public static void main(String[] args) {
// Crie instâncias, compare e imprima
}
}
Saída esperada
Record: CoordenadaRecord[x=1, y=2]
Classe: CoordenadaClasse{x=1, y=2}
Record equals? true
Classe equals? true
Record é imutável, não há setters.
Ver dica
Para a classe, lembre-se de sobrescrever equals e hashCode corretamente. Use Objects.equals e Objects.hash para simplificar.
Mostrar solução
import java.util.Objects;
public class Main {
// Record com validação
public record CoordenadaRecord(int x, int y) {
public CoordenadaRecord {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordenadas não podem ser negativas");
}
}
}
// Classe tradicional com validação e imutabilidade (sem setters)
public static class CoordenadaClasse {
private final int x;
private final int y;
public CoordenadaClasse(int x, int y) {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordenadas não podem ser negativas");
}
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CoordenadaClasse that = (CoordenadaClasse) o;
return x == that.x && y == that.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "CoordenadaClasse{x=" + x + ", y=" + y + "}";
}
}
public static void main(String[] args) {
CoordenadaRecord rec1 = new CoordenadaRecord(1, 2);
CoordenadaRecord rec2 = new CoordenadaRecord(1, 2);
CoordenadaClasse cls1 = new CoordenadaClasse(1, 2);
CoordenadaClasse cls2 = new CoordenadaClasse(1, 2);
System.out.println("Record: " + rec1);
System.out.println("Classe: " + cls1);
System.out.println("Record equals? " + rec1.equals(rec2));
System.out.println("Classe equals? " + cls1.equals(cls2));
System.out.println("Record é imutável, não há setters.");
// cls1.setX(5); // Descomente para ver que a classe poderia ser mutável se tivesse setters
}
}
Passo a passo
- Definimos o record
CoordenadaRecordcom dois componentesxeye um construtor compacto que valida se são não negativos. - Definimos a classe
CoordenadaClassecom camposprivate final int x, y, construtor com a mesma validação, getters,equals,hashCodeetoString. - No
main, criamos duas instâncias de cada tipo com os mesmos valores. - Imprimimos o
toStringde cada uma para mostrar a representação. - Comparamos as instâncias com
equalse imprimimos os resultados (ambostrue). - Comentamos que a classe poderia ter setters, mas o record não permite, reforçando a imutabilidade.
Por que funciona
Records são inerentemente imutáveis e fornecem automaticamente implementações corretas de equals, hashCode e toString. A classe tradicional exige que o programador escreva esses métodos manualmente, o que é propenso a erros. Além disso, records não têm setters, enquanto classes podem ser mutáveis se desejado. A comparação com equals mostra que ambos os tipos podem ser usados como value objects, mas o record é mais conciso e seguro.
Erros comuns
- Esquecer de sobrescrever
equalsehashCodena classe: Isso faz com que a comparação use identidade de referência, não valores.
Errado: não sobrescrever.
Correto: sobrescrever como mostrado. - Declarar campos não
finalna classe e não fornecer setters: Isso ainda permite modificação via reflexão, mas não é o ideal. Prefirafinalpara imutabilidade. - Tentar modificar um record: Records não têm setters e seus campos são
final. Qualquer tentativa de atribuição resulta em erro de compilação.
Errado:rec1.x = 10;
Correto: crie um novo record. - Usar
getClass() != o.getClass()noequalsda classe: Isso é correto, mas se a classe for estendida, pode falhar. Alternativa: usarinstanceofse a classe não for final.
Outra forma de resolver
Para a classe, você pode usar Objects.equals e Objects.hash para simplificar:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CoordenadaClasse)) return false;
CoordenadaClasse that = (CoordenadaClasse) o;
return x == that.x && y == that.y;
}
Mas a versão com getClass() é mais rigorosa. Records são preferíveis quando a imutabilidade e a concisão são desejadas.
Saída esperada
Record: CoordenadaRecord[x=1, y=2]
Classe: CoordenadaClasse{x=1, y=2}
Record equals? true
Classe equals? true
Record é imutável, não há setters.