Formas de reducir el código de las clases POJO de Java

Escrito por picodotdev el , actualizado el .
java planeta-codigo programacion
Enlace permanente Comentarios

La clases POJO o beans de Java son clases que en su mayor parte únicamente tiene la definición de sus propiedades. En Java al ser un lenguaje que promueve el ser explícito requiere definir mucho código que aporta poco valor. Cuando el número de estas clases simples es grande el código aún siendo simple se convierte en un problema de mantenimiento. Las librerías Immutables, Lombok y los records de Java incorporados en la versión 14 permite reducir en gran medida esa cantidad de código, mantener la consistencia y adicionalmente proporcionar clases que implementan el patrón Builder.

Java

Java es un lenguaje verboso además requerir realizar las definiciones de forma explícita y con poco azúcar sintáctico (o como diría algún otro, poco veneno para ratas), esto hace que el número de líneas de código necesarias sea mayor que en otros lenguajes. Que Java sea verboso, explícito y con poco azúcar sintáctico no es necesariamente un inconveniente ya que la mayor parte del tiempo los programadores la dedicamos a leer código ya escrito, sin embargo, a mayor número de líneas mayor tiempo se requiere en mantenerlas.

Un ejemplo claro está en las clases POJO que sieguen las convenciones de los beans con múltiples propiedades donde por cada propiedad es necesario definir un método get y un método set además implementar de forma correcta los métodos equals y hashCode, el método toString, utilizar el patrón builder, múltiples constructores con combinaciones de propiedades o comprobaciones para valores no nulos en parámetros.

Aunque los entornos integrados de desarrollo como IntelliJ o eclipse permiten generar de forma rápida los métodos getter, setter, equals y hashCode cuando son numerosas las clases a generar de esta forma el tiempo necesario para crearlas y mantenerlo es elevado. Todos estos métodos no son complicados pero dado su número también se convierte en un problema de mantenimiento y legibilidad, además de que mayor dificultad para mantener la consistencia en todas las clases.

Y aunque los IDE tiene la capacidad e generar algunos métodos no tienen la funcionalidad de generar las clases del patrón Builder. Implementar un patrón Builder puede complicarse con el número de funcionalidades que se le incorporen.

Para facilitar el mantenimiento y reducir numerosas líneas de código de boilerplate que requiere Java para estas clases, que aún simples sean numerosas y no tengan muchos métodos propios, hay dos librerías utilizables que se encargan mediante anotaciones de generarlas y construir las clases Builder asociadas.

Librerías para reducir el código de las clase POJO de Java

Es habitual que en una aplicación se necesiten clases como simplemente estructuras de datos y objetos para transferir datos o en el caso de implementar DDD y algunos patrones tácticos como los value objects. Clases que que tienen unas pocas propiedades y son inmutables.

Dos librerías que permiten simplificar estas clases son Immutables y Lombok. Ambas se basan en el uso de anotaciones, aunque la implementación en cada una de ellas es diferente, la implementación de Immutables tiene algunas ventajas.

Las anotaciones de estas librerías permiten escribir menos código repetitivo en beans o objetos de transferencia de datos o DTO pero conviene conocer lo que hacen esas anotaciones. Una de las características por las que algunas personas usan otros lenguajes más recientes como Groovy es que en estos requieren menos líneas código para hacer lo mismo que en Java como el caso de los métodos get y set de las propiedades que en Groovy se proporcionan de forma implícita y en Java de forma explícita, con Immutables o Lombok en Java estos métodos también se pueden proporcionar con anotaciones sin tener que codificarlos.

Immutables

Immutables es una librería que permite generar clases y eliminar código repetitivo. Funciona a través de la definición de una interfaz de la clase o una clase abstracta, el uso de anotaciones y un procesador de anotaciones. Al realizar la compilación el procesador de anotaciones genera el código fuente de una clase que implementa la interfaz o clase abstracta y la clase Builder.

Las clases generadas por defecto son inmutables, type-safe, null-safe y thread-safe y se genera una Builder asociada. Permite la personalización de ciertos estilos al generar las clases, soportan serialización con Jackson y Gson.

La anotación principal es @Value.Immutable que permite definir una clase que implemente la interfaz o clase abstracta indicada junto con su clase Builder. Con la anotación @Value.Style se personalizan los valores por defecto de la generación según las convenciones deseadas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package io.github.picodotdev.blogbitix.lombokimmutablesrecord.immutables;

import org.immutables.value.Value;

@Value.Immutable
public interface Car {

    String brand();
    String model();
    String color();
    int doors();
    long kilometers();

    class Builder extends ImmutableCar.Builder {}
    static Builder builder() {
        return new Car.Builder();
    }
}
Car-immutables.java

Lombok

Lombok es una librería popular para el propósito de eliminar todo ese código repetitivo necesario en cada bean o clase Java utilizando varias anotaciones. Usarla no requiere ninguna complicación basta añadirla a la lista de dependencias de compilación y las anotaciones serán procesadas.

La implementación de Lombok se realiza modificando la clase anotada para añadirle las implementaciones de los métodos, modifica el archivo .class que se genera al compilar. Por esta forma de instrumentalizar las clases, los IDEs para ser conscientes de los cambios requieren instalar un complemento que añada el soporte para Lombok.

Lombok proporciona un conjunto de anotaciones, en la documentación se explica detalladamente cuales son y que hace cada una de ellas y un ejemplo de código bastante ilustrativo comparando el código usando las anotaciones y el código Java equivalente.

Además de por modificar el archivo class generado por el compilador y necesitar un complemento en el IDE, hay algunos motivos por los que no usar Lombok. La anotación @NonNull simplemente ahorra el uso de Objects.nonNull(), el uso de la anotación simplemente varia la forma de hacer la comprobación con el efecto dañino de no hacer explícto el código. La anotación @Cleanup no es necesario con la existencia de la sentencia try-with-resources a partir de Java 7. Hacer uso @SneakyThrows no es una buena práctica. @Synchronized es equivalente a usar la palabra reservada syncrhonized en el método. @Log reemplaza una linea de código por otra. @val @var, en Java 10 se implementa de forma nativa con la palabra var. @Getter, @Setter, @ToString, @EqualsAndHashCode, @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor hay mejores alternativas para implementar algunas clases del modelo de dominio con la aproximación de interfaces o clases abstractas que proporciona Immutable. Por la implementación de Lombok tiene o ha tenido problemas de compatibilidad con Java 9.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package io.github.picodotdev.blogbitix.lombokimmutablesrecord.lombok;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class Car {

    private String brand;
    private String model;
    private String color;
    private int doors;
    private long kilometers;
}
Car-lombok.java

Records de Java 14

Los records de Java incorporados desde la versión 14 de Java permiten definir clases inmutables y con un código mínimo para estas clases que básicamente se componen de un conjunto de propiedades. Los recrods son la implementación nativa proporcionada por Java para estas clases, sin embargo, no se proporcionan las clases Builder y estos hay que seguir implementándolos.

1
2
3
4
package io.github.picodotdev.blogbitix.lombokimmutablesrecord.record;

public record Car(String brand, String model, String color, int dors, long kilometers) {
}
Car-record.java

Ejemplo de código

Un programa de ejemplo que hace uso de estas clases es el siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.github.picodotdev.blogbitix.lombokimmutablesrecord;

public class Main {

    public static void main(String[] args) {
        {
            io.github.picodotdev.blogbitix.lombokimmutablesrecord.lombok.Car car = 
                    io.github.picodotdev.blogbitix.lombokimmutablesrecord.lombok.Car.builder().brand("Tesla").model("Model 3").color("black").doors(5).kilometers(0).build();
            car.setKilometers(10);
            System.out.println("Lombok: " + car);
        }

        {
            io.github.picodotdev.blogbitix.lombokimmutablesrecord.immutables.Car car = 
                    io.github.picodotdev.blogbitix.lombokimmutablesrecord.immutables.Car.builder().brand("Tesla").model("Model 3").color("black").doors(5).kilometers(10).build();
            System.out.println("Immutables: " + car);
        }

        {
            io.github.picodotdev.blogbitix.lombokimmutablesrecord.record.Car car = 
                    new io.github.picodotdev.blogbitix.lombokimmutablesrecord.record.Car("Tesla", "Model 3", "black", 5, 0);
            System.out.println("Records: " + car);
        }
    }
}
Main.java
1
2
3
Lombok: Car(brand=Tesla, model=Model 3, color=black, doors=5, kilometers=10)
Immutables: Car{brand=Tesla, model=Model 3, color=black, doors=5, kilometers=10}
Records: Car[brand=Tesla, model=Model 3, color=black, dors=5, kilometers=0]
System.out

Usando Gradle hay que añadir en el archivo de construcción del proyecto build.gradle las dependencias y los procesadores de anotaciones.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
plugins {
    id 'java'
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    annotationProcessor 'org.projectlombok:lombok:1.18.8'
    annotationProcessor 'org.immutables:value:2.8.8'

    implementation 'org.projectlombok:lombok:1.18.8'
    implementation 'org.immutables:value:2.8.8'
}

application {
    sourceCompatibility = '15'
    mainClass = 'io.github.picodotdev.blogbitix.lombokimmutablesrecord.Main'
}

tasks.withType(JavaCompile) {
    options.compilerArgs += "--enable-preview"
}

tasks.withType(Test) {
    jvmArgs += "--enable-preview"
}

tasks.withType(JavaExec) {
    jvmArgs += "--enable-preview"
}
build.gradle
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew run

Comparte el artículo: