Introducción
El método toString()
en JAVA se utiliza para obtener la representación en cadena de un objeto. Sin embargo, crear implementaciones de este método para cada clase puede resultar tedioso y agregar un código innecesario que podría haber sido evitado. Afortunadamente, Project Lombok llega al rescate al permitirnos generar representaciones de cadena consistentes sin el desorden de escribir manualmente el código. En este tutorial, exploraremos cómo auto-generar el método toString()
utilizando Lombok y las diversas opciones de configuración disponibles para ajustar la salida resultante.
1. Resumen
Como sabemos, el método toString()
se utiliza para obtener la representación en cadena de un objeto en JAVA. Project Lombok puede ayudarnos a generar representaciones de cadena consistentes sin el boilerplate, mejorando también la mantenibilidad, especialmente en clases que pueden contener un gran número de campos.
2. Configuración
Comencemos por incluir la dependencia de Project Lombok en nuestro proyecto de ejemplo:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
En nuestros ejemplos, utilizaremos una simple clase POJO Account
con algunos campos para demostrar la funcionalidad y las diversas opciones de configuración.
3. 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()
.
import lombok.ToString;
@ToString
public class Account {
private String id;
private String name;
// getters y setters estándar
}
Por defecto, la anotación @ToString
imprime el nombre de la clase, junto con cada nombre de campo no estático y su valor obtenido por la llamada al getter (si está declarado). Los campos aparecen en el orden de declaración en la clase fuente y están separados por comas.
Ahora, una llamada al método toString()
en una instancia de esta clase genera la siguiente salida:
Account(id=12345, name=An account)
En la mayoría de los casos, esto es suficiente para generar una representación de cadena estándar y útil de los objetos JAVA.
4. Opciones de Configuración
Existen varias opciones de configuración disponibles que nos permiten modificar y ajustar el método toString()
generado. Vamos a explorar estas configuraciones más a fondo.
4.1. toString()
de Superclase
Por defecto, la salida no contiene datos de la implementación del método toString()
de la superclase. Sin embargo, podemos modificar esto configurando el atributo callSuper
en true
:
@ToString(callSuper = true)
public class SavingAccount extends Account {
private String savingAccountId;
// getters y setters estándar
}
Esto produce la siguiente salida que incluye la información de la superclase seguida de los campos y valores de la subclase:
SavingAccount(super=Account(id=12345, name=An account), savingAccountId=6789)
4.2. Omitiendo Nombres de Campo
Como vimos anteriormente, la salida por defecto contiene nombres de campos seguidos de sus valores. Sin embargo, podemos omitir los nombres de campo configurando el atributo includeFieldNames
en false
en la anotación @ToString
:
@ToString(includeFieldNames = false)
public class Account {
private String id;
private String name;
// getters y setters estándar
}
Como resultado, la salida ahora muestra una lista separada por comas de todos los valores de los campos sin los nombres de los campos:
Account(12345, An account)
4.3. Usando Campos en lugar de Getters
Por defecto, los métodos getter proporcionan los valores de los campos para imprimir. No obstante, podemos configurar Lombok para que siempre use los valores de los campos directos en lugar de los getters configurando el atributo doNotUseGetters
en true
:
@ToString(doNotUseGetters = true)
public class Account {
private String id;
private String name;
// getter ignorado
public String getId() {
return "este es el id:" + id;
}
// getters y setters estándar
}
Sin esta configuración, obtendremos la salida que se obtendría invocando a los getters:
Account(id=este es el id:12345, name=An account)
Sin embargo, con el atributo doNotUseGetters
, la salida muestra el valor del campo id
, sin invocar el getter:
Account(id=12345, name=An account)
4.4. Inclusión y Exclusión de Campos
A veces, desearíamos excluir ciertos campos de la representación en cadena, como contraseñas o información sensible. Podemos omitir dichos campos simplemente anotándolos con @ToString.Exclude
:
@ToString
public class Account {
private String id;
@ToString.Exclude
private String name;
// getters y setters estándar
}
Alternativamente, podemos especificar solo los campos que se requieren en la salida. Logramos esto usando @ToString(onlyExplicitlyIncluded = true)
a nivel de clase y luego anotando cada campo requerido con @ToString.Include
:
@ToString(onlyExplicitlyIncluded = true)
public class Account {
@ToString.Include
private String id;
private String name;
// getters y setters estándares
}
Ambos enfoques anteriores producen la siguiente salida con solo el campo id
:
Account(id=12345)
Además, Lombok automáticamente excluye cualquier variable que comience con el símbolo $
. Sin embargo, podemos anular este comportamiento e incluirlas agregando la anotación @ToString.Include
a nivel de campo.
4.5. Ordenación de la Salida
Por defecto, la salida contiene los campos según el orden de declaración en la clase. Sin embargo, podemos ajustar el orden agregando el atributo rank
a la anotación @ToString.Include
.
Por ejemplo, modifiquemos nuestra clase Account
para que el campo id
se renderice primero, independientemente de su posición de declaración en la definición de la clase:
@ToString
public class Account {
private String name;
@ToString.Include(rank = 1)
private String id;
// getters y setters estándar
}
Ahora, el campo id
se renderiza primero en la salida:
Account(id=12345, name=An account)
La salida contiene los miembros de un rango superior primero, seguidos por rangos más bajos. 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.
4.6. Salida de Métodos
Además de los campos, también es posible incluir la salida de un método de instancia que no tome argumentos. Podemos hacer esto marcando el método sin argumentos con @ToString.Include
:
@ToString
public class Account {
private String id;
private String name;
@ToString.Include
String description() {
return "Descripción de la cuenta";
}
// getters y setters estándar
}
Esto añade la description
como clave y su salida como valor a la representación de Account
:
Account(id=12345, name=An account, description=Descripción de la cuenta)
Si el nombre del método coincide con un nombre de campo, entonces el método toma 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.
4.7. Modificando Nombres de Campos
Podemos cambiar cualquier nombre de campo especificando un valor diferente en el atributo name
de la anotación @ToString.Include
:
@ToString
public class Account {
@ToString.Include(name = "identificación")
private String id;
private String name;
// getters y setters estándares
}
Ahora, la salida contiene el nombre alternativo de campo desde el atributo de anotación en lugar del nombre del campo real:
Account(identificación=12345, name=An account)
5. Imprimiendo Arrays
Los arrays se imprimen utilizando el método Arrays.deepToString()
. Esto convierte los elementos del array a sus representaciones de cadena correspondientes. Sin embargo, es posible que el array contenga ya sea una referencia directa o una referencia circular indirecta.
Para evitar la recursión infinita y los errores de tiempo de ejecución asociados, este método renderiza cualquier referencia circular al array desde dentro de sí mismo como “[[…]]”
.
Veamos esto agregando un campo de array Object
a nuestra clase Account
:
@ToString
public class Account {
private String id;
private Object[] relatedAccounts;
// getters y setters estándar
}
El array relatedAccounts
ahora se incluye en la salida:
Account(id=12345, relatedAccounts=[54321, [...]])
Importante, la referencia circular es detectada por el método deepToString()
y renderizada apropiadamente por Lombok, sin provocar ningún StackOverflowError
.
6. Puntos a Recordar
Las siguientes consideraciones son importantes para evitar resultados inesperados:
- En presencia de cualquier método llamado
toString()
en la clase (sin importar el tipo de retorno), Lombok no genera su métodotoString()
. - Diferentes versiones de Lombok pueden cambiar el formato de salida del método generado. En cualquier caso, debemos evitar un código que dependa de analizar la salida del método
toString()
, así que esto no debería ser un problema. - Además, podemos agregar esta anotación a
enum
, produciendo una representación donde el valor delenum
sigue al nombre de la claseenum
, por ejemplo,AccountType.SAVING
.
7. Conclusión
En este artículo, hemos visto cómo utilizar las anotaciones de Lombok para generar una representación en String
de objetos JAVA con un esfuerzo y boilerplate mínimo. Inicialmente, observamos el uso básico, que suele ser suficiente para la mayoría de los casos. Luego, cubrimos una amplia gama de opciones disponibles para ajustar y sintonizar la salida generada, optimizando la forma en que representamos nuestros objetos en cadena.
Además, la facilidad de configuración de Lombok permite a los desarrolladores centrarse más en la lógica y menos en la gestión de código repetitivo, lo que puede llevar a un desarrollo más rápido y limpio. Te invitamos a explorar Lombok y ver cómo puede mejorar tu trabajo en JAVA.