Como sabemos, el método toString() se usa para obtener la representación string de un objeto Java.

El Proyecto Lombok puede ayudarnos a generar representaciones de cadenas coherentes sin el modelo estándar y sin saturar el código fuente. También puede mejorar la capacidad de mantenimiento, especialmente donde las clases pueden contener una gran cantidad de campos.

En este tutorial, veremos cómo generar automáticamente este método y las diversas opciones de configuración disponibles para ajustar aún más la salida resultante.

Comencemos por incluir la dependencia del Proyecto Lombok en nuestro proyecto de muestra:

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

O si usan JDK9+ con module-info.java

<annotationProcessorPaths>
	<path>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.24</version>
	</path>
</annotationProcessorPaths>

En nuestros ejemplos, usaremos una clase POJO “Cuenta” con algunos campos para demostrar la funcionalidad y varias opciones de configuración.

Uso Básico

Podemos anotar cualquier clase con la anotación Lombok @ToString. Esto modifica el bytecode generado y crea una implementación del método toString().

Apliquemos esta anotación a nuestro POJO simple:

@ToString
public class Cuenta {

    private String id;

    private String nombre;

    // getters y setters
}

De forma predeterminada, la anotación @ToString imprime el nombre de la clase, junto con cada nombre de campo no estático y su valor obtenido llamando al getter (si se declara). Los campos también aparecen según el orden de declaración en la clase fuente. Una coma separa los diferentes pares de valores de campo.

Ahora, una llamada al método toString() en una instancia de esta clase genera este resultado:

Account(id=12345, nombre=Juanito Caminante)

En la mayoría de los casos, esto es suficiente para generar una representación string útil y estándar de objetos Java.

Opciones de configuración

Hay varias opciones de configuración disponibles que nos permiten modificar y modificar el método toString() generado. Estos pueden ser útiles en ciertos casos de uso. Echemos un vistazo a estos con un poco más de detalle.

Superclase

De forma predeterminada, la salida no contiene datos de la implementación de la superclase del método toString(). Sin embargo, podemos modificar esto configurando el valor del atributo callSuper en verdadero:

@ToString(callSuper = true)
public class CuentaAhorros extends Cuenta {
    
    private String idCuentaAhorros;

    // getters y setters
}

Esto produce el siguiente resultado con la información de la superclase seguida de los campos y valores de la subclase:

CuentaAhorros(super=Cuenta (id=12345, nombre=Juanito Caminante), idCuentaAhorros=6789)

Es importante destacar que esto solo es realmente beneficioso cuando heredamos una clase que no sea java.lang.Object. La implementación de Objeto de toString() no proporciona mucha información útil. En otras palabras, incluir estos datos solo agrega información redundante y aumenta la verbosidad de salida.

Omitir los nombres de los campos

Como vimos anteriormente, la salida predeterminada contiene nombres de campo seguidos de los valores. Sin embargo, podemos omitir los nombres de campo de la salida configurando el atributo includeFieldNames en falso en la anotación @ToString:

@ToString(includeFieldNames = false)
public class Cuenta {

    private String id;

    private String nombre;

    // getters y setters
}

Como resultado, la salida ahora muestra una lista separada por comas de todos los valores de campo sin los nombres de campo:

Cuenta (12345, Juanito Caminante)

Usar nombres de campo en lugar de getters

Como ya hemos visto, los getters proporcionan los valores de campo para la impresión. Además, si la clase no contiene un método getter para un campo en particular, Lombok accede directamente al campo y obtiene su valor.

Sin embargo, podemos configurar Lombok para usar siempre los valores de campo directos en lugar de los getters configurando el atributo doNotUseGetters en verdadero:

@ToString(doNotUseGetters = true)
public class Cuenta {

    private String id;

    private String nombre;

    // getter ignorado
    public String getId() {
        return "Este es el id:" + id;
    }

    // getters y setters
}

Sin este atributo, obtendríamos el resultado llamando a los getters:

Cuenta(id=Este es el id:12345, nombre=Juanito Caminante)

En cambio, con el atributo doNotUseGetters, la salida en realidad muestra el valor del campo id, sin invocar el getter:

Cuenta(id=12345, nombre=Juanito Caminante)

Incluir y excluir campos

Digamos que queremos excluir ciertos campos de la representación de cadenas, por ejemplo, contraseñas, otra información confidencial o estructuras JSON grandes. Podemos omitir dichos campos simplemente anotándolos con la anotación @ToString.Exclude.

Excluyamos el campo de nombre de nuestra representación:

@ToString
public class Cuenta {

    private String id;

    @ToString.Exclude
    private String nombre;

    // getters y setters
}

Alternativamente, podemos especificar solo los campos que son obligatorios en la salida. Logremos esto usando @ToString(onlyExplicitlyIncluded = true) a nivel de clase y luego anotando cada campo obligatorio con @ToString.Include:

@ToString(onlyExplicitlyIncluded = true)
public class Cuenta {

    @ToString.Include
    private String id;

    private String nombre;

    // getters y setters
}

Ambos enfoques anteriores producen el siguiente resultado con solo el campo id:

Cuenta(id=12345)

Además, la salida de Lombok excluye automáticamente cualquier variable que comience con el símbolo $. Sin embargo, podemos anular este comportamiento e incluirlos agregando la anotación @ToString.Include en el nivel de campo.

Ordenando la salida

De forma predeterminada, la salida contiene campos según el orden de declaración en la clase. Sin embargo, podemos ajustar el orden simplemente agregando el atributo de rango a la anotación @ToString.Include.

Modifiquemos nuestra clase Cuenta para que el campo id se muestre antes que cualquier otro campo, independientemente de la posición de la declaración en la definición de la clase. Podemos lograr esto agregando la anotación @ToString.Include(rank = 1) al campo id:

@ToString
public class Cuenta {

    private String nombre;

    @ToString.Include(rank = 1)
    private String id;

    // getters y setters
}

Ahora, el campo id aparece primero en la salida a pesar de su declaración después del campo de nombre:

Cuenta(id=12345, nombre=Juanito Caminante)

La salida contiene miembros de un rango superior primero, seguidos de rangos inferiores. El valor de rango predeterminado para los miembros sin el atributo de rango es 0. Los miembros con el mismo rango se imprimen de acuerdo con su orden de declaración.

Incluir salida de un metodo

Además de los campos, también es posible incluir la salida de un método de instancia que no acepta argumentos. Podemos hacer esto marcando el método de instancia sin argumentos con @ToString.Include:

@ToString
public class Cuenta {

    private String id;

    private String nombre;

    @ToString.Include
    String descripcion() {
        return "Esta es una cuenta VIP";
    }

    // getters y setters
}

Esto agrega la descripción como clave y su salida como valor a la representación de Cuenta:

Cuenta(id=12345, nombre=Juanito Caminante, descripcion=Esta es una cuenta VIP)

Si el nombre del método especificado coincide con un nombre de campo, entonces el método tiene prioridad sobre el campo. En otras palabras, la salida contiene el resultado de la invocación del método en lugar del valor del campo coincidente.

Modificar los nombres de los campos

Podemos cambiar cualquier nombre de campo especificando un valor diferente en el atributo de nombre de la anotación @ToString.Include:

@ToString
public class Cuenta {

    @ToString.Include(name = "identificador")
    private String id;

    private String nombre;

    // getters y setters
}

Ahora, la salida contiene el nombre de campo alternativo del atributo de anotación en lugar del nombre de campo real:

Cuenta (identificador= 12345, nombre = Juanito Caminante)

Imprimir Arreglos

Las matrices se imprimen utilizando el método Arrays.deepToString(). Esto convierte los elementos de la matriz en sus representaciones de cadena correspondientes. Sin embargo, es posible que la matriz contenga una referencia directa o una referencia circular indirecta.

Para evitar la repetición infinita y sus errores de tiempo de ejecución asociados, este método representa cualquier referencia circular a la matriz desde dentro de sí misma como “[[…]]”.

Veamos esto agregando un campo de matriz de objetos a nuestra clase Cuenta:

@ToString
public class Cuenta {

    private String id;

    private Object[] cuentasRelacionadas;

    // getters y setters
}

La matriz de cuentas relacionadas ahora se incluye en la salida:

Cuenta(id=12345, cuentasRelacionadas=[54321, [...]])

Es importante destacar que la referencia circular es detectada por el método deepToString() y Lombok la procesa adecuadamente, sin causar ningún StackOverflowError.

En fin…

Hay varios detalles que vale la pena mencionar que son importantes para evitar resultados inesperados.

En presencia de cualquier método denominado toString() en la clase (independientemente del tipo de devolución), Lombok no genera su método toString().

Diferentes versiones de Lombok pueden cambiar el formato de salida del método generado. En cualquier caso, debemos evitar el código que se basa en analizar la salida del método toString(). Así que esto no debería ser realmente un problema.

Por último, también podemos agregar esta anotación en las enumeraciones. Esto produce una representación en la que el valor de la enumeración sigue al nombre de la clase de enumeración, por ejemplo, AccounType.SAVING.

Categorized in: