Java Records
Use Java records para criar classes de transporte de dados compactas e imutáveis com accessors, equals e hashCode gerados automaticamente.
Um record é uma classe cujo único trabalho é transportar dados. Você declara os campos uma vez no cabeçalho, e o compilador gera o construtor, os accessors, equals, hashCode e toString para você. O que antes exigia 40 linhas de getters e código repetitivo agora é uma única linha:
public record Point(int x, int y) {}Essa é a classe inteira. new Point(3, 4), p.x(), p.y(), p.equals(other) e p.toString() funcionam e se comportam exatamente como você esperaria.
Os records foram finalizados no Java 16 (após previews nas versões 14 e 15). Esta página aborda o que o compilador gera, como validar e personalizar um record, as regras que os records impõem e como os records funcionam em conjunto com o pattern matching.
Por que os records existem
Antes dos records, toda "classe de dados" precisava de um campo private final por componente, um construtor que os atribuía, um getter por campo, equals e hashCode baseados em valor, e um toString. A maior parte desse código era mecânica e fácil de errar sutilmente (esquecer um campo no equals, retornar o campo errado em um getter, copiar e colar um erro de digitação). Os records condensam tudo isso em um cabeçalho, eliminando tanto a digitação quanto os bugs.
Componentes e accessors
Os argumentos no cabeçalho são chamados de componentes. Cada um se torna:
- Um campo
private finalcom o mesmo nome. - Um método accessor com o mesmo nome (não
getX()— apenasx()).
Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y()); // 3, 4Os nomes coincidem porque os records tratam de expor dados de forma direta. Não há prefixo get porque não há nada a esconder.
equals, hashCode e toString gerados
Dois records são iguais se e somente se forem do mesmo tipo de record e cada componente for igual:
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b)); // true
System.out.println(a); // Point[x=3, y=4]hashCode combina todos os componentes, portanto os records funcionam corretamente como chaves em HashMap e HashSet sem esforço extra.
Construtor compacto
Todo record tem um construtor canônico — aquele cujos parâmetros correspondem aos componentes em ordem. Por padrão, o compilador o escreve para você (ele apenas atribui cada parâmetro ao seu campo). Quando você precisa de validação ou normalização, não é necessário repetir essas atribuições: escreva um construtor compacto, que não tem lista de parâmetros e é executado antes das atribuições implícitas de campos:
public record Range(int low, int high) {
public Range {
if (low > high)
throw new IllegalArgumentException("low > high");
}
}Você também pode reatribuir as variáveis de parâmetro dentro do construtor compacto — os valores finais são o que os campos recebem:
public record Name(String first, String last) {
public Name {
first = first.strip();
last = last.strip();
}
}Adicionando métodos
Os records podem ter quaisquer métodos que você normalmente escreveria — eles simplesmente não podem ter campos de instância adicionais (tudo que sustenta o record deve ser um componente):
public record Point(int x, int y) {
public double distanceTo(Point other) {
int dx = x - other.x;
int dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}Campos estáticos e métodos estáticos são permitidos. Os records também podem implementar interfaces.
O que os records não podem fazer
- Sem herança. Um record implicitamente estende
java.lang.Recorde éfinal— você não pode estender um record e um record não pode estender outra classe. - Sem estado mutável. Todos os componentes são
final. Se você quiser mutação, use uma classe normal. - Sem campos de instância fora dos componentes. Você não pode incluir um
private int cache;extra.
Essas restrições são o ponto central. Um record promete "Eu sou apenas meus componentes, e nada mais." Essa promessa é o que torna equals, hashCode e serialização seguros para geração automática.
Quando usar um record
Os records são adequados quando você escreveria uma pequena classe de dados imutáveis — DTOs, objetos de configuração, tipos de retorno que agrupam alguns valores, tuplas em switches de pattern matching. Eles são inadequados quando o tipo tem identidade, possui estado mutável ou é a raiz de uma hierarquia.
Um exemplo prático
Records no pattern matching
Como os componentes de um record são públicos e ordenados, o compilador também sabe como desmontar um record. Um record pattern vincula cada componente a uma variável em uma única etapa, o que torna os records a forma natural para dados que você percorre com switch:
sealed interface Shape permits Circle, Rect {}
record Point(int x, int y) {}
record Circle(Point center, double r) implements Shape {}
record Rect(Point a, Point b) implements Shape {}
static String describe(Shape s) {
return switch (s) {
case Circle(Point c, double r) -> "circle r=" + r + " at " + c.x() + "," + c.y();
case Rect(Point a, Point b) -> "rect " + a + " to " + b;
};
}O pattern Circle(Point c, double r) verifica que s é um Circle e extrai seus componentes em uma única expressão. Veja Java pattern matching para o recurso completo, incluindo patterns aninhados.
O que vem a seguir
Os records fixam uma classe a um conjunto fixo de dados. O próximo capítulo apresenta as sealed classes, que fixam uma hierarquia a um conjunto fixo de subtipos — a peça que faltava para modelar famílias fechadas semelhantes a tipos de dados algébricos em Java. Continue para Java sealed classes.