Método toString() en Java
Aprende a sobrescribir toString() en clases Java para obtener representaciones de texto útiles en logs y depuración.
toString es el método al que recurren System.out.println, la concatenación de cadenas y tu depurador cuando necesitan representar un objeto como texto. El valor por defecto — heredado de Object — es el nombre de la clase más un hash hexadecimal ilegible. Sobrescribir toString es una de las cosas más baratas y de mayor rentabilidad que puedes hacer para las personas que leerán tus logs y trazas de pila (con frecuencia, una versión futura de ti mismo).
Dónde se invoca
Casi nunca llamas a toString directamente. Se invoca de forma implícita cada vez que un objeto se convierte en texto:
Point p = new Point(3, 4);
System.out.println(p); // calls p.toString()
String msg = "got " + p; // calls p.toString()
log.info("point: {}", p); // calls p.toString()Siempre que un Object aparece en un contexto que espera un String, se ejecuta toString. Por eso el valor por defecto es tan decepcionante — aparece en todas partes y no te dice nada útil.
El valor por defecto
Object.toString() devuelve getClass().getName() + "@" + Integer.toHexString(hashCode()). Así, una clase simple imprime algo como com.example.Point@1540e19d. La clase está bien; el hexadecimal es el hash de identidad, que es inútil cuando intentas averiguar qué contienen los campos del objeto.
Una buena sobrescritura
Un buen toString es breve, inequívoco y contiene los datos que un lector querría ver:
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}Algunas convenciones que vale la pena seguir:
- Incluye el nombre de la clase. Cuando una línea de log mezcla objetos, saber de qué tipo es importa.
- Usa un formato estable y parseable.
Clase[campo=valor, campo=valor]es lo que usan los records y la mayoría de las bibliotecas modernas de Java. Es legible y fácil de buscar con grep. - Muestra los campos que importan — normalmente los que están en
equals. Omite los campos enormes (un blob de 10 MB, unaConnection). - No incluyas secretos. Contraseñas, tokens, datos personales — déjalos fuera, o enmascáralos.
toStringacaba en los archivos de log.
Con String.format o bloques de texto
Para más de tres o cuatro campos, String.format (o un bloque de texto) resulta más legible que la concatenación:
@Override
public String toString() {
return String.format("User[id=%d, name=%s, role=%s]", id, name, role);
}No lo hagas costoso
toString se llama más a menudo de lo que crees — los frameworks de log renderizan los argumentos de forma perezosa, pero lo suficientemente rápido como para que un toString O(n²) sobre una lista de 100.000 elementos definitivamente ralentice tu servicio. Mantén la salida acotada. El toString de una colección debería limitarse a un comienzo razonable, con ... (N more) para el resto.
No lances excepciones desde toString
Lanzar una excepción en toString convierte una línea de log rutinaria en una NullPointerException en algún lugar inesperado — y el problema subyacente que intentabas registrar se pierde. Protégete contra los valores null:
@Override
public String toString() {
return "Order[id=" + id + ", customer=" + (customer == null ? "<none>" : customer.name()) + "]";
}Los records lo obtienen gratis
Si tu clase es un simple contenedor de datos, los records ya generan un buen toString:
record Point(int x, int y) {}
System.out.println(new Point(3, 4)); // Point[x=3, y=4]Por lo tanto, sobrescribir toString es principalmente algo que haces para las clases que no son records.
Un ejemplo completo
Qué viene a continuación
toString produce una descripción de un objeto. El siguiente capítulo trata sobre producir una copia de uno — clone, Cloneable y las incómodas decisiones de diseño en torno a las copias superficiales frente a las profundas. Continúa con clonación de objetos en Java.