Metodo toString() in Java
Fai l'override di toString() nelle classi Java per ottenere rappresentazioni testuali utili nel logging e nel debugging.
toString è il metodo che System.out.println, la concatenazione di stringhe e il tuo debugger utilizzano quando devono rendere un oggetto come testo. Il comportamento predefinito — ereditato da Object — è il nome della classe più un hash esadecimale illeggibile. Fare l'override di toString è una delle cose più economiche e con il miglior rapporto costo-beneficio che puoi fare per chi leggerà i tuoi log e le tue stack trace (spesso una versione futura di te stesso).
Dove viene chiamato
Quasi mai chiami toString direttamente. Viene invocato implicitamente ogni volta che un oggetto viene convertito in testo:
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()Ovunque un Object appaia in un contesto che richiede una String, toString viene eseguito. Ecco perché il comportamento predefinito è così deludente — compare ovunque e non fornisce alcuna informazione utile.
Il comportamento predefinito
Object.toString() restituisce getClass().getName() + "@" + Integer.toHexString(hashCode()). Quindi una classe semplice viene stampata come com.example.Point@1540e19d. Il nome della classe va bene; l'esadecimale è l'hash code di identità, che è inutile quando stai cercando di capire cosa contengono i campi dell'oggetto.
Un buon override
Un buon toString è breve, non ambiguo e contiene i dati che un lettore vorrebbe vedere:
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}Alcune convenzioni da seguire:
- Includi il nome della classe. Quando una riga di log mescola oggetti, sapere di che tipo si tratta è importante.
- Usa un formato stabile e analizzabile.
Class[field=value, field=value]è quello che i record e la maggior parte delle moderne librerie Java utilizzano. È leggibile e comodo per grep. - Mostra i campi che contano — di solito quelli presenti in
equals. Tralascia campi enormi (un blob da 10MB, unaConnection). - Non includere segreti. Password, token, PII — lasciali fuori, o mascherali.
toStringfinisce nei file di log.
Con String.format o blocchi di testo
Per più di tre o quattro campi, String.format (o un blocco di testo) è più leggibile della concatenazione:
@Override
public String toString() {
return String.format("User[id=%d, name=%s, role=%s]", id, name, role);
}Non renderlo costoso
toString viene chiamato più spesso di quanto pensi — i framework di logging rendono gli argomenti in modo lazy ma abbastanza velocemente da far sì che un toString O(n²) su una lista di 100k elementi rallenterà sicuramente il tuo servizio. Mantieni l'output limitato. Il toString di una collezione dovrebbe essere limitato a una testa ragionevole, con ... (N more) per il resto.
Non lanciare eccezioni da toString
Lanciare un'eccezione in toString trasforma una normale riga di log in una NullPointerException da qualche parte inaspettata — e il problema sottostante che stavi cercando di registrare va perso. Proteggi contro i null:
@Override
public String toString() {
return "Order[id=" + id + ", customer=" + (customer == null ? "<none>" : customer.name()) + "]";
}I record ne ottengono uno gratuitamente
Se la tua classe è un semplice contenitore di dati, i record generano già un buon toString:
record Point(int x, int y) {}
System.out.println(new Point(3, 4)); // Point[x=3, y=4]Quindi fare l'override di toString è principalmente qualcosa che si fa per le classi non-record.
Un esempio pratico
Cosa c'è dopo
toString produce una descrizione di un oggetto. Il prossimo capitolo tratta la produzione di una copia di esso — clone, Cloneable e le scelte di design problematiche riguardo alle copie superficiali e profonde. Continua con clonazione di oggetti Java.