Record Patterns – Java 21
A versão 21 do Java foi aprimorada com padrões de registro (Record Patterns) para desconstruir valores de um objeto record. Padrões de registro chegou a linguagem para permitir uma forma poderosa, declarativa e combinável de navegação e processamento de dados.
Mas antes de abordar Record Patterns, vamos relembrar o que é um record.
O que é um record?
No Java 16 foi lançado o recurso record, uma maneira mais concisa e expressiva de definir classes que armazenam dados. Vamos analisar a diferença entre o código criado com base em uma classe e um record.
Quando criamos uma classe, precisamos definir explicitamente os atributos, métodos getters, setters e, se necessário, um método construtor com os argumentos referentes aos atributos da classe. A seguir temos o código da classe Person que exemplifica isso:
public class Person {
private String firstName;
private String lastName;
private int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
}Contudo, com o recurso record temos a possibilidade de reduzir drasticamente a quantidade de código apresentado na classe Person:
public record Person(String firstName, String lastName, int age) {
}Com isso, o record Person já possui automaticamente:
- Um construtor que inicializa os campos.
- Métodos acessores (getters) para cada campo (
firstName(),lastName(),age()). - Implementações padrão dos métodos
equals(),hashCode()etoString().
Embora se possa perceber a redução no código padrão, a verdadeira intenção dos records é fornecer uma forma transparente de definir dados. Isto é permitido através da colocação de diversas restrições nos registros, incluindo:
- Todos os campos são
final - As classes de registro são implicitamente
final - Campos de declaração de instância são proibidos
Além disso, o record também fornece alguns recursos:
- Todas as classes de record incluem construtores canônicos (default)
- Todas as classes de record incluem acessadores de componentes
Dessa forma, podemos observar alguns conceitos importantes sobre o uso de record:
Imutabilidade: Os records são imutáveis, o que significa que seus campos não podem ser alterados após a criação do objeto.
Concisão: A definição de um record é mais enxuta, pois ele encapsula automaticamente os detalhes comuns de uma classe de dados.
Menos boilerplate: Com records, você economiza tempo e linhas de código, evitando repetições.
Em resumo, se você precisa apenas armazenar dados e não precisa de mutabilidade, os records são uma escolha excelente e mais elegante no Java. Essas restrições e recursos tornaram os registros candidatos ideais para adicionar desconstrutores.
Padrões de Registros (Record Patterns)
Os padrões de registro foram propostos como um recurso de visualização pelo JEP 405 e entregues no JDK 19 , e visualizados uma segunda vez pelo JEP 432 e entregues no JDK 20 . No JDK 21 foi marcado com status de entregue pela JEP 440.
O Record Patterns é um recurso introduzido no Java 19 que permite desmontar (ou desconstruir) valores de registros (records). Ele serve para combinar padrões de correspondência com registros, facilitando a extração de componentes dos registros e a navegação em dados de maneira declarativa. Com o Record Patterns, podemos verificar se um valor corresponde a um tipo de registro específico e vincular variáveis aos componentes desse registro. Isso torna o código mais conciso e legível, especialmente quando trabalhamos com registros aninhados ou genéricos.
Desconstrutores e Component Matching
Os desconstrutores, conforme o próprio nome sugere, representam o inverso dos construtores. Enquanto um construtor se encarrega de gerar e adicionar dados a um objeto, um desconstrutor foca em extrair informações dele, tal como demonstrado neste caso:
public record Vehicle(String brand, String model, String licensePlate) {
}
class Main {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle("Honda", "Fit 1.4", "JAV-1621");
if(vehicle instanceof Vehicle(String brand, String model, String licensePlate)) {
System.out.println(brand + " - " + model + ": " + licensePlate);
}
}
}Observe no código acima, o qual temos o record Vehicle. No método principal da classe Main foi adicionada uma instancia para criar o objeto com a definição de seus valores. Na linha abaixo, temos um teste condicional que verifica se o objeto é do tipo Vehicle usando Component Matching com o operador instanceof.
O Component Matching ou componente de correspondência (String brand, String model, String licensePlate) é usado para extrair os valores dos campos do objeto Vehicle. Se o objeto for uma instância de Vehicle, os valores dos campos serão extraídos e o bloco de código dentro do if será executado.
O conceito de desconstrutor é usado acessando diretamente os argumentos do método construtor na linha com o System.out. Ou seja, o objeto é desconstruído para se ter acesso aos valores de seus atributos.
Além disso, com o componente de correspondência, também chamado de padrão de correspondência, o tipo de uma variável só precisa ser compatível com o aquilo que foi declarado no componente record. No exemplo abaixo, var é usado em substituição aos tipos String e int e ainda assim, o teste condicional será verdadeiro.
if(vehicle instanceof Vehicle(var marca, var modelo, var placa)) {
System.out.println(marca + " - " + modelo + ": " + placa);
}Outro ponto importante a ser observado está relacionado aos nomes dos argumentos do construtor de Vehicle. Veja que os nomes foram alterados na declaração do método, na linha do teste condicional, e mesmo assim a o objeto será desconstruído e os valores de seus atributos estarão acessíveis no System.out.
Você pode usar as técnicas de padrões de registro no switch/case, que agora oferece suporte à correspondência de padrões com Java 21. Veja no código apresenta um exemplo de como proceder:
public interface Interest {}
public record SimpleInterest(double principal, double rate, int time) implements Interest {}
public record CompoundInterest(double principal, double rate, int time) implements Interest {}
public class Main {
public static void main(String[] args) {
var res1 = calculate(new SimpleInterest(1000.0, 3.5, 2));
System.out.println(res1);
var res2 = calculate(new CompoundInterest(580.0, 5.5, 3));
System.out.println(res2);
}
static Object calculate(Interest interest) {
return switch(interest) {
case SimpleInterest(var p, var r, var t) -> p * r * t / 100;
case CompoundInterest(var p, var r, var t) -> p * Math.pow(1 + r / 100, t) - p;
default -> "Invalid interest type.";
};
}
}O código apresenta a interface Interest que é implementada pelos records SimpleInterest e CompoundInterest. No método de execução criamos a instancia para os objetos SimpleInterest e CompoundInterest. Essas instancias são adicionadas como parâmetro do método estático calculate().
Em calculate() temos o bloco switch, onde as linhas case retornarão o resultado dos cálculos para cada tipo de objeto. Se o objeto de instancia for SimpleInterest o primeiro case será verdadeiro, caso o objeto de instancia armazenado na variável interest seja CompoundInterest, então o segundo case será verdadeiro.
Observe que a variável interest é diretamente comparada em cada um dos case, como se existisse explicitamente um teste do tipo instance of participando desta operação. Quando os tipos forem os mesmo, então o objeto declarado no case será desconstruído e suas variáveis preenchidas com os valores contidos na variável interest. Podendo assim, usá-las como partes do calculo que vem a seguir.
Conclusão
Em resumo, record e padrões de registro são fundamentais na evolução da programação orientada a dados no Java. Esta narrativa está em constante desenvolvimento, como exemplificado pelo JEP 443, que trouxe padrões e variáveis nomeadas como um recurso visual no Java 21. Recomenda-se consultar a seção de leitura adicional do artigo de Brian Goetz, “Data-Oriented Programming in Java“, para compreender melhor como isso será implementado.


