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.