Lombok: deixe o código repetitivo de lado

Lombok: deixe o código repetitivo de lado

O Lombok é uma biblioteca que usa anotações para reduzir parte do código repetitivo que precisamos para construir uma aplicação. Por exemplo, nas classes que representam o domínio de negócio nós geralmente definimos vários atributos privados e seus getters e setters públicos para expor esses atributos ao mundo exterior. Via de regra, esses métodos não possuem regra de negócio e poderiam ser criados ou definidos automaticamente. Esse é um dos exemplos onde o Lombok pode ser usado.

Nesse post mostraremos algumas das funcionalidades do Lombok e como ele pode ser usado rotineiramente no desenvolvimento de aplicações. O projeto de exemplo que utilizaremos está disponível no Github.

Instalação

O Lombok é basicamente um Jar e pode ser adicionado como dependência no projeto. Caso seja utilizado Maven basta adicionar a seguinte dependência ao pom.xml:

<dependency>  
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.16</version>
    <scope>provided</scope>
</dependency>  

Caso seja utilizado o Gradle adicione a dependência ao build.gradle:

dependencies {  
    compileOnly 'org.projectlombok:lombok:1.16.16'
}

Note que no caso do Gradle se a anotações do Lombok forem utilizadas nas classes de teste é necessário incluir a dependência testOnly 'org.projectlombok:lombok:1.16.16' no build.gradle também.

Se quiser instalar o Lombok no Eclipse, faça o download do arquivo Jar do projeto para a sua máquina, execute java -jar lombok.jar e siga as instruções. No caso do IntelliJ você deve instalar o plugin do Lombok direto pelo IDE e habilitar o processamento de anotações no projeto, conforme a imagem abaixo:

Projeto de exemplo

Para exemplificar o uso do Lombok vamos criar um projeto de teste bem simples. Imagine que temos um blog de música com algumas buscas de bandas e no nosso código fizemos alguns testes unitários para garantir que as buscas estão corretas. Para os testes criaremos uma massa de dados representando algumas bandas. Mostraremos como o Lombok pode auxiliar nesse cenário.

Inicialmente criaremos a classe Banda, que possui os atributos nome, estilo e integrantes:

public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;

    public String getNome() {
        return nome;
    }

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

    public Estilo getEstilo() {
        return estilo;
    }

    public void setEstilo(Estilo estilo) {
        this.estilo = estilo;
    }

    public List<String> getIntegrantes() {
        return integrantes;
    }

    public void setIntegrantes(List<String> integrantes) {
        this.integrantes = integrantes;
    }
}

Para criar a massa de dados usada nos testes basicamente precisamos criar objetos da classe Banda, que é feito da seguinte forma:

private static Banda criarBanda(String nome, Estilo estilo, String ... integrantes) {

    Banda banda = new Banda();
    banda.setNome(nome);
    banda.setEstilo(estilo);

    banda.setIntegrantes(Arrays.asList(integrantes));

    return banda;
}

public static Banda RUSH = criarBanda("Rush", ROCK_N_ROLL,  
    "Alex Lifeson", "Geddy Lee", "Neil Peart");

public static Banda METALLICA = criarBanda("Metallica", ROCK_N_ROLL,  
    "James", "Kirk", "Lars", "Cliff The Eternal");

public static Banda CREAM = criarBanda("Cream", ROCK_N_ROLL,  
    "Jack Bruce", "Eric Clapton", "Ginger Baker");

[...]

Esse é o nosso código básico. Vamos agora usar o Lombok pra dar um up!

Removendo Getters e Setters

As anotações @Getter e @Setter são usadas nos atributos ou na declaração de classes para gerar getters e setters defaults. Se usados em classes os métodos serão criados para todos os atributos não estáticos. Os setters não serão gerados para atributos final. A classe Banda com essas anotações fica assim:

@Getter
@Setter
public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;
}

Que sensação de limpeza na classe, não? 👻

Nesse caso, os métodos gerados serão públicos, mas podemos mudar esse comportamento e definir o nível de acesso desejado. Por exemplo, podemos definir que todos os getters são públicos mas os setters são protected:

@Getter
@Setter(AccessLevel.PROTECTED)
public class Banda {

[...]

}

Por padrão, métodos setter não devolvem valor algum, são do tipo void. O Lombok permite que seja feita uma configuração para que a implementação desses métodos devolva uma referência para o próprio objeto, ou seja, this. Para fazer isso, basta criar um arquivo chamado lombok.config na raiz do projeto, com o seguinte conteúdo:

lombok.accessors.chain = true  

dessa forma, o código do método criarBanda pode ser alterado para:

private static Banda criarBanda(String nome, Estilo estilo, String ... integrantes) {  
    return new Banda()
        .setNome(nome)
        .setEstilo(estilo)
        .setIntegrantes(Arrays.asList(integrantes));
}

ou se quisermos podemos até parar de usar o método criarBanda:

public static Banda RUSH = new Banda().setNome("Rush")  
    .setEstilo( ROCK_N_ROLL)
    .setIntegrantes(Arrays.asList("Alex Lifeson", "Geddy Lee", "Neil Peart"));

Fazendo essa modificação temos uma forma muito mais fluída e simples de construir os objetos.

Definindo construtores

Repare que usamos o método criarBanda para auxiliar na criação dos objetos de teste, mas poderíamos facilmente substituir isso pela definição de um construtor na classe Banda que recebe como parâmetro todos os atributos. Ao invés de escrever esse construtor vamos usar o Lombok.

@Getter
@Setter
@AllArgsConstructor
public class Banda {  
    [...]
}

A anotação @AllArgsConstructor cria um construtor com um parâmetro para cada atributo da classe. Dessa forma, podemos alterar a construção dos objetos para:

public static Banda RUSH = new Banda("Rush", ROCK_N_ROLL,  
    Arrays.asList("Alex Lifeson", "Geddy Lee", "Neil Peart"));

Note que tivemos que usar o Arrays.asList para passar os integrantes, isso porque o Lombok define o tipo de cada parâmetro igual ao tipo do atributo.

Agora a nossa classe contém um construtor com vários parâmetros mas não temos mais o construtor padrão! Mas como o Lombok isso não é problema, basta usar a anotação @NoArgsConstructor para ganhar o construtor default.

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Banda {

[...]

}

O Lombok tem mais uma anotação para gerar construtores que é a @RequiredArgsConstructor. Essa anotação vai gerar um construtor com um parâmetro para cada atributo declarado como final ou anotado com @NonNull, outra anotação do Lombok para verificar campos obrigatórios. Caso nenhum atributo atenda aos requisitos citados será gerado um construtor default.

As três anotações apresentadas também permitam que seja definida a visibilidade dos construtores: @AllArgsConstructor(AccessLevel.PRIVATE), @NoArgsConstructor(AccessLevel.PACKAGE), @RequiredArgsConstructor(AccessLevel.PUBLIC) e assim por diante.

Equals, hashCode e toString

Também é possível usar o Lombok para criar os métodos equals, hashCode e toString. Para fazer isso basta anotar a classe com @EqualsAndHashCode e @ToString.

@Getter
@Setter
@AllArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode
@ToString
public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;
}

Os 3 métodos gerados vão usar nas suas implementações todos os atributos definidos na classe. Mas e se não quisermos esse comportamento, por exemplo, suponha que somente o atributo nome seja relevante nos métodos equals, hashCode e os atributos nome e estilo no toString? Podemos definir isso com os atributos das anotações. Veja:

@Getter
@Setter
@AllArgsConstructor
@RequiredArgsConstructor
@EqualsAndHashCode(of = {"nome"})
@ToString(exclude = {"integrantes"})
public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;
}

As duas anotações possuem os atributos of e exclude. of usará a lista de atributos informados para compor o método e exclude para desconsiderar os atributos informados. Também temos a opção de usar o método da classe pai na geração dos métodos através do atributo callSuper.

@ToString(exclude = {"integrantes"}, callSuper = true)

Várias anotações...tem como melhorar isso?

Agora temos uma classe com várias anotações no lugar de um monte de código repetitivo! Será que não é possível melhorar isso? Sim, com certeza! O Lombok tem a anotação @Data que basicamente substitui as anotações @Getter, @Setter, @RequiredArgsConstructor, @ToString e @EqualsAndHashCode. Olha que beleza, podemos usar essa anotação para substituir todas as outras:

@Data
@AllArgsConstructor
public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;
}

Muito melhor não? Mas note quando usamos @Data perdemos as customizações das outras anotações que ela engloba. Ainda assim, temos a possibilidade adicionar essas outras anotações e configurá-las conforme a necessidade. Repare também que @AllArgsConstructor ainda foi mantida pois a funcionalidade definida por essa anotação não é incluída na @Data.

A conclusão é que @Data te dá algumas coisas mas tira outras, portanto você deverá ponderar se o seu uso é benéfico ou não. Pela minha experiência vale a pena usá-la, mesmo que seja só para não ter que declarar @Getter, @Setter e @RequiredArgsConstructor separadamente.

Gerando Log

Existem diversas bibliotecas de Log disponível, mas independente disso a forma como declaramos o logger nas classes é quase sempre parecido com esse:

public class FiltroPowerTrio implements FiltroBanda {  
    // constante usada como logger da classe
    private static final Logger LOG = LoggerFactory
        .getLogger(FiltroPowerTrio.class);

    [...]
}

Em seguida fazemos a geração do log da seguinte forma: LOG.debug("Filtrando power trios..."). O Lombok nos fornece como facilidade a geração dessa declaração do logger. Por exemplo, se estivermos usando o Slf4j, basta anotar a classe com @Slf4j e depois usar a variável log para gerar os logs:

@Slf4j
public class FiltroPowerTrio implements FiltroBanda {

    [...]

    @Override
    public List<Banda> filtrar(List<Banda> bandas) {
        log.debug("Filtrando power trios...");

        return filterByPredicate(bandas,
            FiltroPowerTrio::isPowerTrio);
    }
}

Ah, mas eu não uso o Slf4j para log, e agora? Não tem problema, o Lombok tem opção para: @CommonsLog, @JBossLog, @Log (usa o java.util.logging), @Log4j, @Log4j2 e @XSlf4j. Escolha o seu sabor favorito!

Simples, não?

Construindo objetos de uma forma mais simples

Criar um objeto e configurar os seus atributos pode ser algo meio tedioso! Através da anotação @Builder, o Lombok é capaz de gerar um Builder para configurar os objetos e consequentemente facilitar a vida! Basicamente anote a sua classe com @Builder e seja feliz! Vamos alterar a classe Banda para fazer uso desse recurso.

@Data
@Builder
public class Banda {  
    private String nome;
    private Estilo estilo;
    private List<String> integrantes;
}

Lembrando que estávamos criando os objetos da massa de teste da seguinte forma:

public static Banda RUSH = new Banda("Rush",  
    ROCK_N_ROLL, Arrays.asList("Alex Lifeson", "Geddy Lee", "Neil Peart"));

E com o builder podemos criar o objeto dessa forma:

public static Banda RUSH = Banda.builder()  
    .nome("Rush")
    .estilo(ROCK_N_ROLL)
    .integrantes(Arrays.asList("Alex Lifeson", "Geddy Lee", "Neil Peart"))
    .build();

Note primeiramente foi criado o método estático builder() na classe Banda que devolve uma instância de um builder que foi gerado pelo Lombok. Para cada atributo da classe Banda foi criado um método no builder com o mesmo nome e responsável por atribuir o valor ao respectivo atributo. Assim, podemos preencher os atributos usando somente o seu próprio nome, como por exemplo: nome("RUSH"). No final, após informar o valor de todos os atributos desejados ao builder, basta chamar o método build() para construir e devolver o nosso objeto banda preenchido! Mágico!!

Mas podemos ainda fazer algo mais legal! Vamos usar outra anotação do Lombok, a @Singular.

@Data
@Builder
public class Banda {  
    private String nome;
    private Estilo estilo;

    @Singular
    private List<String> integrantes;
}

Com essa anotação é criado um método no builder que permite inserir elemento por elemento nas lista de integrantes sem ter que passar a lista inteira. Com essas modificações a criação de objetos do tipo Banda fica:

public static Banda RUSH = Banda.builder()  
    .nome("Rush")
    .estilo(ROCK_N_ROLL)
    .integrante("Alex Lifeson")
    .integrante("Geddy Lee")
    .integrante("Neil Peart")
    .build();

Conforme mostra a documentação, a anotação @Builder pode ser declarada em classes, construtores e métodos, e é muito muito flexível e complexa ao mesmo tempo. Nesse post mostramos somente um caso simples declarando-a na classe. Recomendo que você ao menos olhe como utilizar a anotação em construtores pois é muito comum utilizá-la dessa forma.

Conclusão

O Lombok é muito interessante por possibilitar que menos código seja escrito e, consequentemente, tenhamos menos bugs no código. Por isso só já vale muito a pena utilizá-lo. Outro ponto, aí talvez seja algo mais pessoal, é que eu fico chocado quando abro uma classe de entity - sim, você pode usar o Lombok com JPA, Hibernate e essas coisas - por exemplo, com 10 atributos e a classe tem 100 linhas só com getters e setters, parece tão contraprodutivo isso! Mas aí você pode dizer: "ah, mas o IDE gera isso automático", sim gera, mas ponto é que continua sendo chocante ver tanta linha de código explicitamente fazendo algo que não precisaria estar no meio do seu código que deveria fazer coisas mais importantes! Sabemos que os atributos precisarão ser expostos com esses métodos e que isso é um padrão, mas se é assim, por que ter que declará-los toda vez?

Os builders também são interessantes para ajudar a deixar o código mais legível. Repare a diferença entre usar um builder e chamar os setters um a um para preencher os atributos de um objeto.

Agora um ponto que não é negativo mas requer atenção: você precisa saber o que o Lombok está fazendo! Por exemplo, você pode ter muito problema usando @ToString em uma classe com um atributo do tipo List que contém objetos de outro tipo também anotado com @ToString e assim por diante! No final você pode acabar imprimindo um grafo gigante com informação inútil, carregar o seu banco de dados inteiro na memória ou até fazendo com que o seu programa entre em loop e você perca umas horinhas para descobrir isso! Delícia né? Então, a recomendação é entender como ele funciona antes de sair usando! Tomando as devidas precauções você será o Lombok como um grande aliado.

Mas é isso! Aprenda, use Lombok e boa sorte nos códigos doidos da vida :-)

Subscribe to