Pular para o conteúdo principal

Java Records

 


Java Records

Imutável, Simples e limpa



Esta funcionalidade da linguagem apareceu pela primeira vez na versão 14 como experimental e assim continuou até a versão 15. Agora liberada de forma definitiva no Java 16.


O objetivo é ser possível ter classes que atuam como portadores transparentes de dados imutáveis. Os registros podem ser considerados tuplas nominais. Ou seja, após criado, um record não pode mais ser alterado.


Records oferece uma uma sintaxe compacta para declarar classes que são portadores transparentes para dados imutáveis superficiais visando reduzir significamente o detalhamento dessas classes e irá melhorar a capacidade de leitura e manutenção do código.


Vamos seguir um exemplo de uma classe chamada Pessoa. O primeiro exemplo vamos utilizar o modo tradicional.


public class Pessoa {

private String nome;
private int idade;

public Pessoa(String nome, int idade) {
super();
this.nome = nome;
this.idade = idade;
}

public String getNome() {
return nome;
}

public void setNome(String nome) {
this.nome = nome;
}

public int getIdade() {
return idade;
}

public void setIdade(int idade) {
this.idade = idade;
}

@Override
public int hashCode() {
return Objects.hash(idade, nome);
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Pessoa other = (Pessoa) obj;
return idade == other.idade && Objects.equals(nome, other.nome);
}

@Override
public String toString() {
return "Pessoa [nome=" + nome + ", idade=" + idade + "]";
}

}


Perceba que a classe possui apenas dois atributos (nome e idade). Geramos os métodos getters e setters, equals, hashCode e toString. Também um construtor para facilitar a criação do objeto. A classe ficou com mais de 50 linhas.


Agora vamos implementar um teste simples para esta classe, verificando as suas “funcionalidades”. Geralmente não testamos este tipo de classe quando não existe regra de negócio envolvida. E também existem bibliotecas que facilitam esta tarefa caso queira garantir que ninguém irá escrever regra de negócio no get e set.


class PessoaTest {

@Test
void testGetNome() {
Pessoa pessoa = new Pessoa("Java", 25);

assertEquals("Java", pessoa.getNome());
}

@Test
void testSetNome() {
Pessoa pessoa = new Pessoa();
pessoa.setNome("Java");

assertEquals("Java", pessoa.getNome());
}

@Test
void testGetIdade() {
Pessoa pessoa = new Pessoa("Java", 25);

assertEquals(25, pessoa.getIdade());
}

@Test
void testSetIdade() {
Pessoa pessoa = new Pessoa();
pessoa.setIdade(25);

assertEquals(25, pessoa.getIdade());
assertNull(pessoa.getNome());
}

@Test
void testEqualsObject() {
Pessoa pessoa1 = new Pessoa("Java", 25);
Pessoa pessoa2 = new Pessoa("Java", 25);

assertEquals(pessoa1, pessoa2);
}

@Test
void testHashCode() {
Pessoa pessoa1 = new Pessoa("Java", 25);
Pessoa pessoa2 = new Pessoa("Java", 25);

assertEquals(pessoa1.hashCode(), pessoa2.hashCode());

}

@Test
void testToString() {
Pessoa pessoa = new Pessoa("Java", 25);
assertEquals("Pessoa [nome=Java, idade=25]", pessoa.toString());
}



Todos os testes passando.


Agora vamos transformar a classe em record e verificar se todos os testes continuam funcionando.


Iniciando o refactor, vamos trocar a palavra chave class por record e excluir todo o restante do conteúdo. Isso mesmo, deixe a “classe” como no exemplo abaixo:


public record Pessoa(String nome, int idade) {

}


Agora vejamos nosso teste quebrado, que na verdade nem compila mais pois não temos os métodos getters, setters e o construtor sem parâmetros. Abaixo um dos erros de compilação:


java: cannot find symbol

  symbol:   method getNome()

  location: variable pessoa of type com.giacom.Pessoa



A primeira alteração na classe de teste é trocar os métodos getters pelo nome do atributo, pois o tipo record oferece acesso somente pelo nome do atributo, então apenas trocamos o getNome por nome por exemplo.


Agora a classe de testes possui alguns erros referentes ao construtor sem parâmetros e os métodos setters. Como vimos anteriormente, records são imutáveis, então não pode ser alterada posteriormente a sua criação e por isso não faz sentido termos estes tipos de método.


O que pode ser feito neste caso então? Simples, apenas remova este tipo de teste de métodos setter, pois não teremos mais. E quanto ao construtor default? Isso deve ser analisado a cada necessidade, pois não há como setar estas informações posteriormente. E com um pequeno ajuste no teste toString(), nossos testes estão passando.




Mas ainda é possível ter um construtor para poder manipular algum dado por exemplo:


public record Pessoa3(String nome) {

  public Pessoa3 {
      nome = nome.toUpperCase();
  }

}


Independente do valor passado (desde que não seja nulo), sempre será transformado em maiúsculo. Um construtor sem parâmetros também pode ser utilizado, porém ele deve chamar outro construtor para preencher os demais atributos:


public record Pessoa(String nome, int idade) {

  public Pessoa {
      Objects.nonNull(nome);
  }

  public Pessoa(String nome) {
      this(nome, 0);
  }

  public Pessoa() {
      this("Indefinido", 0);
  }

}


E no teste ficaria assim:


@Test
void testNovaPessoa() {
  Pessoa pessoa = new Pessoa();
  assertEquals("Indefinido", pessoa.nome());
  assertEquals(0, pessoa.idade());
}



E sobre os métodos equals, hashCode e toString? O Records gera automaticamente estes métodos para nós.


Abaixo algumas restrições referentes a este tipo de classe:


  • Uma Record class não possui uma cláusula extends

  • Uma Record class não pode ser abstrata

  • Os atributos derivados da classe Record são todos finais

  • Não pode declarar campos de instância

  • Não pode declarar métodos nativos


Além das restrições acima, uma classe de registro se comporta como uma classe normal:


  • Uma instância de Record é criada com a expressão new

  • Pode ser declarada como um tipo genérico

  • Pode declarar métodos, atributos e inicializadores estáticos

  • Pode declarar métodos de instância

  • Pode implementar interfaces

  • Utilizar annotations

  • Podem ser serializados e desserializado



Todas as classes do tipo Record estendem da classe abstrata java.lang.Record 


Reflection API

Foram adicionados dois novos métodos na classe java.lang.Class:

  • RecordComponent [] getRecordComponents () - Retorna uma matriz de objetos java.lang.reflect.RecordComponent. Os elementos deste array correspondem aos componentes de uma instância do tipo Record, na mesma ordem em que aparecem na declaração. Informações adicionais podem ser extraídas de cada elemento da matriz, incluindo seu nome, anotações e método de acesso.

  • boolean isRecord () - Retorna verdadeiro se a classe fornecida foi declarada como um Record. (Comparado ao isEnum.)


Records vs Lombok

O uso de Lombok sempre é uma polêmica. Alguns desenvolvedores amam e outros odeiam. Os Records não substituem o Lombok, pois embora forneçam muitos recursos interessantes e sejam organizados no código, o Lombok ainda tem muito mais recursos do que Records, por exemplo o @Builder

Para mim a vantagem dos Records é que ele é nativo da linguagem e não é necessário utilizar uma biblioteca de terceiros, trazendo mais facilidade para os desenvolvedores. O Frameworks e outras bibliotecas irão se adaptar com o tempo. É possível utilizar o Lombok com Records, porém deve ser usado com cuidado.

Mãos na massa

Caso queira experimentar o Java 16, ele pode ser baixado através do site da Oracle e também do projeto AdoptOpenJDK.

As IDE’s que tem suporte até o momento:

Deixe nos comentários se já está utilizando este recurso. Um abraço e até a próxima!

Referências:

https://openjdk.java.net/jeps/395
https://www.baeldung.com/java-record-keyword
https://angiejones.tech/deserializing-api-responses-into-java-records/
https://medium.com/@gaozhixiang/records-vs-lombok-in-java-15-193306340ca0
https://softwaregarden.dev/en/posts/new-java/records/vs-lombok/

Comentários

Postar um comentário

Postagens mais visitadas deste blog

Java 8 ao 18: Mudanças mais importantes na plataforma Java

    Vamos rever muitas das mudanças mais importantes na plataforma Java que aconteceram entre a versão 8 (2014) e 18 (2022)   O Java 8 foi lançado em março de 2014 e o Java 18 em março de 2022. São 8 anos de progresso, 203 JEPs (JDK Enhancement Proposals ), entre essas duas versões. Neste post, revisaremos as mudanças mais importantes e discutiremos os benefícios e desafios da adoção de versões mais recentes do JDK para novos aplicativos e para os mais antigos compilados com versões mais antigas. Desde a versão 9, o Java tem novos recursos a cada 6 meses e é muito difícil acompanhar essas novas mudanças. A maioria das informações na internet descreve as mudanças entre as duas últimas versões do Java. No entanto, se você estiver em uma situação semelhante à minha, não está usando uma das versões mais recentes do Java, mas uma das várias versões anteriores (Geralmente 8 ou 11 que são as versões de suporte estendido). Então é útil saber quais novos recursos foram adicionados d

O suporte de longo prazo e o que o LTS significa para o ecossistema Java

A arte do suporte de longo prazo e o que o LTS significa para o ecossistema Java Aqui está o que o Java 17 tem em comum com o Java 11 e o Java 8. Em junho de 2018, há pouco mais de três anos, a Oracle e outros participantes do ecossistema Java anunciaram uma mudança no modelo de cadência de lançamento para Java SE. Em vez de ter um lançamento principal planejado a cada dois ou quatro anos (que geralmente se torna de três a quatro anos), um novo modelo de lançamento de recursos de seis meses seria usado: a cada três anos, um lançamento seria designado como Long-Term Support (LTS) e receba apenas atualizações trimestrais de segurança, estabilidade e desempenho. Esse padrão foi emprestado descaradamente do modelo de lançamento do Mozilla Firefox, mas o ajustou para ficar mais alinhado com os requisitos de uma plataforma de desenvolvimento. A primeira versão do Java lançada sob esse modelo foi o Java SE 11. O lançamento do Java SE 17, o segundo lançamento do LTS sob o novo