Fonte: abhinavpandey.dev Neste tutorial, abordaremos os fundamentos do uso de registros em Java. Os registros foram introduzidos no Java 14 como uma forma de remover o código clichê em torno da criação de objetos Value e, ao mesmo tempo, aproveitar as vantagens de objetos imutáveis.
1. Conceitos básicos
Antes de entrarmos nas entradas em si, vejamos o problema que elas resolvem. Para fazer isso, teremos que lembrar como os objetos de valor eram criados antes do Java 14.1.1. Objetos de valor
Objetos de valor são parte integrante dos aplicativos Java. Eles armazenam dados que precisam ser transferidos entre camadas de aplicação. Um objeto de valor contém campos, construtores e métodos para acessar esses campos. Abaixo está um exemplo de um objeto de valor:public class Contact {
private final String name;
private final String email;
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
1.2. Igualdade entre objetos Value
Os objetos de valor também podem fornecer uma maneira de compará-los quanto à igualdade. Por padrão, Java compara a igualdade dos objetos comparando seus endereços de memória. Contudo, em alguns casos, objetos contendo os mesmos dados podem ser considerados iguais. Para implementar isso, podemos substituir os métodos equals e .hashCode . Vamos implementá-los para a classe Contact :public class Contact {
// ...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Contact contact = (Contact) o;
return Object.equals(email, contact.email) &&
Objects.equals(name, contact.name);
}
@Override
public int hashCode() {
return Objects.hash(name, email);
}
}
1.3. Imutabilidade de objetos de valor
Os objetos de valor devem ser imutáveis. Isso significa que devemos limitar as maneiras pelas quais podemos alterar os campos de um objeto. Isto é aconselhável pelos seguintes motivos:- Para evitar o risco de alterar acidentalmente o valor do campo.
- Para garantir que objetos iguais permaneçam os mesmos ao longo de suas vidas.
- tornou os campos privados e finais .
- forneceu apenas um getter para cada campo (sem setters ).
1.4. Registrando objetos Value
Muitas vezes precisamos registrar os valores contidos nos objetos. Isso é feito fornecendo um método toString . Sempre que um objeto é registrado ou impresso, o método toString é chamado . A maneira mais fácil aqui é imprimir o valor de cada campo. Aqui está um exemplo:public class Contact {
// ...
@Override
public String toString() {
return "Contact[" +
"name='" + name + '\'' +
", email=" + email +
']';
}
}
2. Reduza modelos com registros
Como a maioria dos objetos de valor tem as mesmas necessidades e funcionalidades, seria bom simplificar o processo de criação deles. Vejamos como as gravações ajudam a conseguir isso.2.1. Convertendo a classe Person em Record
Vamos criar uma entrada da classe Contact que tenha a mesma funcionalidade da classe Contact definida acima.public record Contact(String name, String email) {}
A palavra-chave record é usada para criar uma classe Record . Os registros podem ser processados pelo chamador da mesma forma que uma classe. Por exemplo, para criar uma nova instância de entrada, podemos usar a palavra-chave new .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");
2.2. Comportamento padrão
Reduzimos o código para uma linha. Vamos listar o que inclui:-
Os campos de nome e e-mail são privados e finais por padrão.
-
O código define um “construtor canônico” que utiliza campos como parâmetros.
-
Os campos são acessíveis através de métodos do tipo getter - name() e email() . Não há setter para campos, portanto os dados no objeto tornam-se imutáveis.
-
Implementado o método toString para imprimir os campos assim como fizemos para a classe Contact .
-
Métodos equals e .hashCode implementados . Eles incluem todos os campos, assim como a classe Contact .
2.3 Construtor canônico
O construtor padrão pega todos os campos como parâmetros de entrada e os define como campos. Por exemplo, o Construtor Canônico padrão é mostrado abaixo:public Contact(String name, String email) {
this.name = name;
this.email = email;
}
Se definirmos um construtor com a mesma assinatura na classe de gravação, ele será usado no lugar do construtor canônico.
3. Trabalhando com registros
Podemos alterar o comportamento da entrada de diversas maneiras. Vejamos alguns casos de uso e como alcançá-los.3.1. Substituindo implementações padrão
Qualquer implementação padrão pode ser alterada substituindo-a. Por exemplo, se quisermos alterar o comportamento do método toString , podemos substituí-lo entre chaves {} .public record Contact(String name, String email) {
@Override
public String toString() {
return "Contact[" +
"name is '" + name + '\'' +
", email is" + email +
']';
}
}
Da mesma forma, podemos substituir os métodos equals e hashCode .
3.2. Kits de construção compactos
Às vezes queremos que os construtores façam mais do que apenas inicializar campos. Para fazer isso, podemos adicionar as operações necessárias à nossa entrada no Construtor Compacto. É denominado compacto porque não necessita definir inicialização de campo ou lista de parâmetros.public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
Observe que não há lista de parâmetros e a inicialização do nome e do email ocorre em segundo plano antes da verificação ser realizada.
3.3. Adicionando Construtores
Você pode adicionar vários construtores a um registro. Vejamos alguns exemplos e limitações. Primeiro, vamos adicionar novos construtores válidos:public record Contact(String name, String email) {
public Contact(String email) {
this("John Doe", email);
}
// replaces the default constructor
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
No primeiro caso, o construtor padrão é acessado usando a palavra-chave this . O segundo construtor substitui o construtor padrão porque possui a mesma lista de parâmetros. Neste caso, a entrada em si não criará um construtor padrão. Existem várias restrições aos construtores.
1. O construtor padrão deve sempre ser chamado de qualquer outro construtor.
Por exemplo, o código abaixo não será compilado:public record Contact(String name, String email) {
public Contact(String name) {
this.name = "John Doe";
this.email = null;
}
}
Esta regra garante que os campos sejam sempre inicializados. Também é garantido que as operações definidas no construtor compacto sejam sempre executadas.
2. Não é possível substituir o construtor padrão se um construtor compacto for definido.
Quando um construtor compacto é definido, um construtor padrão é criado automaticamente com inicialização e lógica de construtor compacto. Neste caso, o compilador não nos permitirá definir um construtor com os mesmos argumentos do construtor padrão. Por exemplo, neste código a compilação não acontecerá:public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
3.4. Implementando Interfaces
Como acontece com qualquer classe, podemos implementar interfaces em registros.public record Contact(String name, String email) implements Comparable<Contact> {
@Override
public int compareTo(Contact o) {
return name.compareTo(o.name);
}
}
Nota importante. Para garantir a imutabilidade completa, os registros não podem ser herdados. As inscrições são finais e não podem ser expandidas. Eles também não podem estender outras classes.
3.5. Adicionando Métodos
Além dos construtores, que substituem métodos e implementações de interface, também podemos adicionar quaisquer métodos que desejarmos. Por exemplo:public record Contact(String name, String email) {
String printName() {
return "My name is:" + this.name;
}
}
Também podemos adicionar métodos estáticos. Por exemplo, se quisermos ter um método estático que retorne uma expressão regular com a qual possamos verificar o e-mail, podemos defini-lo conforme mostrado abaixo:
public record Contact(String name, String email) {
static Pattern emailRegex() {
return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
}
}
3.6. Adicionando campos
Não podemos adicionar campos de instância a um registro. No entanto, podemos adicionar campos estáticos.public record Contact(String name, String email) {
private static final Pattern EMAIL_REGEX_PATTERN = Pattern
.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
static Pattern emailRegex() {
return EMAIL_REGEX_PATTERN;
}
}
Observe que não há restrições implícitas em campos estáticos. Se necessário, podem estar disponíveis publicamente e não ser definitivos.
GO TO FULL VERSION