Ejemplo de patrón Builder para las clases Record de Java

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

Java se ha caracterizado por ser un lenguaje verboso y necesitar declarar todo de forma explícita, esto tiene la ventaja de que el código es muy explícito sin apenas convenciones que es necesario conocer para saber cómo se comporta el código. Por el contrario esta verbosidad requiere declarar gran cantidad de código que se hace repetitivo en muchas clases. Los Records de Java 16 permite declarar clases de datos en muy pocas líneas de código, esto lo consiguen introduciendo algunas convenciones que son de uso común en el lenguaje. Un aspecto que no resuelven los Records es el crear clases Builder que son una necesidad asociada para esas clases de datos.

Java

Java desde la versión 8 ha incorporado muchas novedades y con el ciclo de desarrollo de una nueva versión cada seis meses las novedades han sido numerosas y cada poco tiempo. Una de las novedades disponibles en la versión de Java 16 ha sido una especialización de clase, las clases Record.

Aún con todas las novedades que se han incorporando sigue habiendo algunas necesidades que al no estar cubiertas por el propio lenguaje o una clase del JDK, una de ellas relacionada con las clases Record es utilizar el patrón de diseño Builder.

Las clases Record de Java

Las clases Record incorporadas en Java son muy útiles ya que simplifican enormemente el código de esas clases que prácticamente son contenedores de datos. Las clases Record evitan tener que declarar explícitamente los métodos getter para cada una de las propiedades así como los métodos hashCode y equals, métodos que hay implementar correctamente, también evitan declarar el método toString muy útiles para el correcto funcionamiento cuando las clases se añaden en colecciones.

 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
public class Point {
   private final int x;
   private final int y;

   Point(int x, int y) {
       this.x = x;
       this.y = y;
   }

   int x() { return x; }
   int y() { return y; }

   public boolean equals(Object o) {
       if (!(o instanceof Point)) return false;
       Point other = (Point) o;
       return other.x == x && other.y == y;
   }

   public int hashCode() {
       return Objects.hash(x, y);
   }

   public String toString() {
       return String.format("Point[x=%d, y=%d]", x, y);
   }
}
records-2.java

La definición de una clase Record lo único que requieren prácticamente es declarar las propiedades y sus tipos, con esta definición la clase implícitamente implementa los métodos getter, hashCode, equals y toString, esta clase Record es equivalente a la anterior pero en muchas menos líneas de código.

1
2
public record Point(int x, int y) {}

records-1.java

Alternativas a Records en versiones anteriores de Java

Usar una de las últimas versiones de Java no es posible en el caso de una aplicación con mucho código existente que usa una versión anterior, a veces no tanto por pasar una nueva versión en el código ya que Java se caracteriza por mantener la compatibilidad hacia atrás sino por alguna dependencia antigua que no es compatible con una versión de Java más reciente o por el entorno de ejecución de la aplicación que ejecutarla sobre una versión más moderna de Java requiere actualizar versiones de librerías que requieren cambios en el código.

Dos librerías como alternativa a los Records utilizables en versiones anteriores de Java 16 son Immutables y Lombok. Ambas proporcionan soporte para evitar mucho del código de las clases Java que se suelen utilizar como contenedores de datos, además proporcionan soporte para crear Builders a partir de ellas.

Aunque Immutables y Lombok son librerías que proporcionan una funcionalidad similar su implementación entre ellas es muy diferente, la implementación de Immutables se realiza generando código con un procesador de anotaciones y Lombok manipula el bytecode de las clases. La aproximación de Immutables es más limpia y potencialmente menos problemática que Lombok pero Lombok es una librería muy popular y sigue utilizándose.

Librería para utilizar el patrón Builder sobre clases Record

No habiendo ninguna restricción para utilizar una versión de Java a partir de la 16 no es necesaria una alternativa a los Records ni requiere dependencias adicionales. Aunque los Records dan solución a una necesidad de crear clases de datos que suelen tener todas las aplicaciones no cubren todas las necesidades como es el caso de crear instancias con una clase Builder.

Hay una librería RecordBuilder para disponer de la funcionalidad del patrón Builder para las clases Record. Con leer el archivo README.md del repositorio Git es más que suficiente para conocerlo todo sobre como usarlo.

Ejemplo de patrón Builder con clase Record

El uso de la librería RecordBuilder para generar clases que implementan el patrón Builder es muy sencillo una vez están definidas las clases Record, basta con anotarlas con la anotación RecordBuilder.

1
2
3
4
5
6
7
8
package io.github.picodotdev.bloblogbitix.javarecordbuilder;

import java.util.Collection;

import io.soabase.recordbuilder.core.RecordBuilder;

@RecordBuilder
public record Book(String title, Collection<String> authors, String isbn, int year, int pages) {}
Book.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package io.github.picodotdev.bloblogbitix.javarecordbuilder;

import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<String> authors = List.of("Pedro Igor Silva", "Stian Thorgersen");
        Book book = BookBuilder.builder()
                .title("Keycloak - Identity and Access Management for Modern Applications ")
                .authors(authors)
                .isbn("978-1800562493")
                .year(2021)
                .pages(362)
                .build();

        System.out.println(book);
    }
}
Main.java
1
2
Book[title=Keycloak - Identity and Access Management for Modern Applications , authors=[Pedro Igor Silva, Stian Thorgersen], isbn=978-1800562493, year=2021, pages=362]

System.out

Como ocurre con cualquier otra librería hay que incluirla como dependencia del proyecto en este caso con la herramienta de construcción Gradle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
plugins {
    id 'application'
}

repositories {
    mavenCentral()
}

dependencies {
    annotationProcessor 'io.soabase.record-builder:record-builder-processor:34'
    compileOnly 'io.soabase.record-builder:record-builder-core:34'
}

application {
    mainClass = 'io.github.picodotdev.bloblogbitix.javarecordbuilder.Main'
}
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: