1. Comparação entre record e class: quais são as principais diferenças?
Em Java, temos duas formas principais de descrever nossos tipos de dados: por meio de classes comuns (class) e por meio de classes record (record). À primeira vista, ambas as opções permitem armazenar e processar dados. Mas, se olharmos um pouco mais fundo, há mais diferenças do que pode parecer!
Tabela de diferenças: class vs record
| Característica | Classe comum (class) | Classe record (record) |
|---|---|---|
| Mutabilidade | Qualquer: pode tornar campos final ou não | Imutável: todos os campos são final |
| Herança | Pode herdar (extends), não é final por padrão | Sempre final, não pode ser superclasse |
| Campos | Qualquer: estáticos, não estáticos, final ou não-final, quaisquer tipos | Apenas componentes do record (private final), além de campos estáticos |
| Getters/setters | Escrevemos manualmente (ou geramos com Lombok) | Getters são criados automaticamente (o nome do campo é o nome do método), não há setters |
| equals/hashCode/toString | Normalmente escrevemos/geramos (equals, hashCode, toString) | Gerados automaticamente com base em todos os componentes |
| Construtores | Quaisquer, quantos forem necessários | Um principal (para todos os componentes), é possível adicionar um construtor compacto |
| Interfaces | Pode implementar | Pode implementar |
| Métodos adicionais | Quaisquer | É possível adicionar, mas apenas métodos (não campos) |
| Uso em coleções | É possível, mas é preciso implementar corretamente equals/hashCode | Perfeito para chaves/valores, tudo já está implementado |
Exemplo ilustrativo
Classe comum:
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
@Override
public String toString() { /* ... */ }
}
Classe record:
public record Person(String name, int age) { }
Pronto! Uma única linha de código — e obtemos o mesmo (e até melhor). E sem riscos de esquecer de implementar algo importante.
2. Restrições das classes record
As classes record não são apenas “sintaxe curta”, mas uma abstração própria com regras rígidas. Vamos analisá-las em detalhes.
Record é sempre final
Por definição, uma classe record é sempre final. Isso significa que você não pode criar uma subclasse de um record:
public record Point(int x, int y) { }
// public class ColoredPoint extends Point { } // Erro de compilação!
Se você precisa estender o comportamento, use classes comuns ou composição (encapsule o record em uma classe).
Record não pode ser superclasse
Uma classe record não pode ser pai de outras classes; ela é sempre final. Isso é lógico: se fosse possível, alguém poderia adicionar um campo mutável — e toda a ideia de “dados imutáveis” cairia por terra.
Apenas campos final (componentes)
Todos os componentes do record são declarados no cabeçalho e, por padrão, são private final. Você não pode adicionar campos não estáticos no corpo do record:
public record User(String login, String email) {
// int counter; // Erro! Não é permitido adicionar campos não estáticos
static int totalUsers = 0; // Pode, é um campo estático
}
Sem setters
Uma classe record não pode ter setters para componentes. Qualquer tentativa de adicionar um método como setX(int x) será inútil: você não conseguirá alterar o valor do campo após a criação do objeto.
public record Point(int x, int y) {
// public void setX(int x) { this.x = x; } // Erro: não é possível alterar um campo final
}
Sem construtor vazio
Uma classe record sempre tem apenas um construtor principal, que aceita valores para todos os componentes. Não é possível criar um record sem informar todos os dados:
Point p = new Point(1, 2); // OK
// Point p = new Point(); // Erro: não há construtor sem parâmetros
Sem inicializadores não estáticos
Uma classe record não pode conter inicializadores não estáticos (aqueles escritos entre chaves fora dos métodos):
public record User(String login) {
// { /* ... */ } // Erro: inicializadores não estáticos são proibidos
}
Restrições de herança
Uma classe record não pode herdar explicitamente outra classe (exceto java.lang.Record, que é oculto como a classe base de todos os record). Mas implementar interfaces — pode sim!
public interface Printable {
void print();
}
public record Book(String title) implements Printable {
@Override
public void print() {
System.out.println("Imprimindo o livro: " + title);
}
}
Não serve para lógica de negócio complexa
record é sobre dados, não sobre comportamento. Se o seu objeto tem lógica complexa, estado mutável, “ciclo de vida” ou um monte de dependências — o record não vai ajudar. É melhor usar uma classe comum.
3. Quando usar classes record?
- DTO (Data Transfer Object): para transferir dados imutáveis entre camadas da aplicação, serviços, microsserviços ou controladores REST (por exemplo, em respostas JSON).
- Value Object: objetos definidos apenas por seus valores.
- Chaves e valores em coleções: quando é importante a implementação correta de equals e hashCode (por exemplo, para uso em HashMap ou Set).
- Resultados de cálculos: quando é preciso retornar vários valores de um método de uma só vez (por exemplo, record Pair<T, U>(T first, U second)).
Exemplo: DTO para controlador REST
public record UserDto(String login, String email) { }
Agora você pode retornar com segurança um objeto desse tipo do controlador, sem medo de que alguém altere seus campos.
Exemplo: chave para HashMap
public record Point(int x, int y) { }
Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");
// Tudo funciona corretamente: equals e hashCode já estão implementados!
4. Quando NÃO usar classes record
- Estado mutável: se pelo menos um campo precisar mudar após a criação do objeto.
- Lógica complexa: se o objeto tiver comportamento complexo, muitos métodos, objetos aninhados com estado mutável.
- Herança: se for necessária uma hierarquia de classes, classes base abstratas, sobrescrita de métodos.
- Entidades de negócio: por exemplo, objetos que vivem no banco de dados e têm um identificador único.
Exemplo: quando é preciso uma classe comum
public class Account {
private String id;
private int balance;
public Account(String id, int balance) {
this.id = id;
this.balance = balance;
}
public void deposit(int amount) { balance += amount; }
public void withdraw(int amount) { balance -= amount; }
// getters, setters, equals, hashCode, toString...
}
Aqui fica claro que o estado do objeto muda — record não é adequado.
5. Exemplos práticos: escolhendo entre record e class
Exemplo 1: record — escolha ideal
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
}
- O retângulo é definido apenas pela largura e altura.
- Não é necessário alterar esses valores após a criação.
- É possível adicionar um método útil area().
- O restante o Java faz por você.
Exemplo 2: class — a melhor opção
public class MutableRectangle {
private int width;
private int height;
public MutableRectangle(int width, int height) {
this.width = width;
this.height = height;
}
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int area() { return width * height; }
}
Precisa alterar as dimensões do retângulo após a criação? Use uma classe comum.
6. Erros comuns ao trabalhar com classes record
Erro nº 1: tentativa de adicionar um campo não estático.
Uma classe record não permite declarar campos não estáticos fora da lista de componentes. Se tentar, o compilador emitirá um erro. Por exemplo:
public record City(String name) {
// int population; // Erro!
}
Erro nº 2: tentar adicionar um setter.
Record não suporta setters para componentes. Qualquer tentativa de alterar o valor de um campo após a criação do objeto resulta em erro de compilação.
Erro nº 3: tentar herdar um record ou herdar de um record.
Record é sempre final. Não é possível herdar de um record, e um record não pode herdar outra classe (além do java.lang.Record oculto).
Erro nº 4: usar record para objetos mutáveis.
Se você pretende mudar o estado do objeto após a criação — record não é para você! Use uma classe comum.
Erro nº 5: esquecer as restrições do construtor.
Uma classe record deve ter obrigatoriamente um construtor que receba valores para todos os componentes. Não existe construtor sem parâmetros!
GO TO FULL VERSION