Usando a anotação @Singular com Lombok Builders
1. Visão geral
A biblioteca Lombok fornece uma ótima maneira de simplificar objetos de dados. Um dos principais recursos deProject Lombok é o@Builder annotation, que cria automaticamente classes Builder para criar objetos imutáveis. No entanto, preencher coleções em nossos objetos pode ser complicado com as classesBuilder geradas pelo Lombok.
Neste tutorial, veremos a anotação@Singular, quehelps us to work with collections in our data objects. também impõe boas práticas, como veremos.
2. Construtores e coleções
As classesBuilder facilitam a construção de objetos de dados imutáveis com sua sintaxe simples e fluente. Vejamos um exemplo de classes anotadas com a anotação@Builder do Lombok:
@Getter
@Builder
public class Person {
private final String givenName;
private final String additionalName;
private final String familyName;
private final List tags;
}
Agora podemos criar instâncias dePerson usando o padrão builder. Observe aqui que a propriedadetags é umList. Além disso, o Lombok@Builder padrão fornecerá métodos para definir essa propriedade, assim como para as propriedades que não estão na lista:
Person person = Person.builder()
.givenName("Aaron")
.additionalName("A")
.familyName("Aardvark")
.tags(Arrays.asList("fictional","incidental"))
.build();
Essa é uma sintaxe viável, mas bastante desajeitada. Podemos criar a coleção inline, como fizemos acima. Ou, podemos declarar com antecedência. De qualquer maneira, isso interrompe o fluxo de nossa criação de objetos. É aqui que a anotação@Singular é útil.
2.1. Usando a anotação@Singular comLists
Vamos adicionar outroList ao nosso objetoPerson e anotá-lo com@Singular. Isso nos dará uma visão lado a lado de um campo que está anotado e outro que não é. Assim como a propriedadetags geral, adicionaremos uma lista deinterests ao nossoPerson:
@Singular private final List interests;
Agora podemos construir uma lista de valores, um de cada vez:
Person person = Person.builder()
.givenName("Aaron")
.additionalName("A")
.familyName("Aardvark")
.interest("history")
.interest("sport")
.build();
O construtor armazenará cada elemento internamente em umListe criará oCollection apropriado quando invocarmosbuild().
2.2. Trabalhando com outros tipos deCollection
Ilustramos@Singular trabalhando comjava.util.List aqui, masit can also be applied to other Java Collection classes. Vamos adicionar mais alguns membros ao nossoPerson:
@Singular private final Set skills;
@Singular private final Map awards;
UmSet se comportará da mesma forma que umList, no que diz respeito aBuilders - podemos adicionar elementos um por um:
Person person = Person.builder()
.givenName("Aaron")
.skill("singing")
.skill("dancing")
.build();
ComoSet não suporta duplicatas, precisamos estar cientes de que adicionar o mesmo elemento várias vezes não criará vários elementos. OBuilder lidará com esta situação com tolerância. Podemos adicionar um elemento várias vezes, mas oSet criado terá apenas uma ocorrência do elemento.
Maps são tratados de forma ligeiramente diferente, com os métodos de exposiçãoBuilder que usam uma chave e um valor dos tipos apropriados:
Person person = Person.builder()
.givenName("Aaron")
.award("Singer of the Year", LocalDate.now().minusYears(5))
.award("Best Dancer", LocalDate.now().minusYears(2))
.build();
Como vimos comSets, o construtor é tolerante com chavesMap duplicadas e usará o último valor se a mesma chave for atribuída mais de uma vez.
3. Nomenclatura de métodos@Singular
Até agora, contamos com um pouco de mágica na anotação@Singular sem chamar a atenção para ela. O próprioBuilder fornece um método para atribuir a coleção inteira de uma vez que usa a forma plural - “awards“, por exemplo. The extra methods added by the @Singular annotation use the singular form - por exemplo, “award“.
Lombok é inteligente o suficiente para reconhecer palavras plurais simples, em inglês, onde elas seguem um padrão regular. Em todos os exemplos que usamos até agora, ele apenas remove os últimos 's'.
Ele também saberá que, para algumas palavras que terminam em "es", remova as duas últimas letras. Sabe, por exemplo, que “grama” é o singular de “gramíneas” e que “uva”, e não “grap”, é o singular de “uvas”. Em alguns casos, porém, temos que ajudá-lo.
Vamos construir um modelo simples de um mar, contendo peixes e ervas marinhas:
@Getter
@Builder
public class Sea {
@Singular private final List grasses;
@Singular private final List fish;
}
Lombok pode lidar com a palavra "gramíneas", mas é perdido com "peixe". Em inglês, as formas singular e plural são as mesmas, estranhamente. Este código não será compilado e teremos um erro:
Can't singularize this name; please specify the singular explicitly (i.e. @Singular("sheep"))
Podemos resolver as coisas adicionando um valor à anotação para usar como o nome do método singular:
@Singular("oneFish") private final List fish;
Agora podemos compilar nosso código e usar oBuilder:
Sea sea = Sea.builder()
.grass("Dulse")
.grass("Kelp")
.oneFish("Cod")
.oneFish("Mackerel")
.build();
Nesse caso, escolhemosoneFish() bastante artificial, mas o mesmo método pode ser usado com palavras não padrão que possuem um plural distinto. Por exemplo, umList dechildren pode ser fornecido com um métodochild().
4. Imutabilidade
Vimos como a anotação@Singular nos ajuda a trabalhar com coleções no Lombok. Além de oferecer conveniência e expressividade, também pode nos ajudar a manter nosso código limpo.
Objetos imutáveis são definidos como objetos que não podem ser modificados depois de criados. A imutabilidade é importante em arquiteturas reativas, por exemplo, porque nos permite passar um objeto para um método com garantia de nenhum efeito colateral. The Builder pattern is most commonly used as an alternative to POJO getters and setters in order to support immutability.
Quando nossos objetos de dados contêm classesCollection, pode ser fácil deixar a imutabilidade escorregar um pouco. As interfaces de coleção de base -List,Set eMap - todas têm implementações mutáveis e imutáveis. Se confiarmos no construtor Lombok padrão, podemos passar acidentalmente uma coleção mutável e modificá-la:
List tags= new ArrayList();
tags.add("fictional");
tags.add("incidental");
Person person = Person.builder()
.givenName("Aaron")
.tags(tags)
.build();
person.getTags().clear();
person.getTags().add("non-fictional");
person.getTags().add("important");
Tivemos que trabalhar muito neste exemplo simples para cometer o erro. Se tivéssemos usadoArrays.asList(), por exemplo, para construir a variáveltags, teríamos obtido uma lista imutável de graça, e chamadas paraadd() ouclear() iriam jogue umUnsupportedOperationException.
Na codificação real, é mais provável que ocorra um erro se a coleção for passada como parâmetro, por exemplo. No entanto,it’s good to know that with @Singular, we can work with the base Collection interfaces and get immutable instances when we call build().
5. Conclusão
Neste tutorial, vimos como a anotação do Lombok@Singular fornece uma maneira conveniente de trabalhar com as interfacesList,SeteMap usando o padrão Builder. O padrão Builder suporta imutabilidade e@Singular nos fornece suporte de primeira classe para isso.
Como de costume, os exemplos de código completos estão disponíveisover on GitHub.