Considerazioni su Java toString ()

Anche gli sviluppatori Java alle prime armi sono consapevoli dell'utilità del metodo Object.toString () che è disponibile per tutte le istanze di classi Java e può essere sovrascritto per fornire dettagli utili riguardo a qualsiasi particolare istanza di una classe Java. Sfortunatamente, anche gli sviluppatori Java esperti a volte non sfruttano appieno questa potente funzionalità Java per una serie di motivi. In questo post del blog, guardo l'umile Java toString()e descrivo i semplici passaggi che si possono intraprendere per migliorare l'utilitarismo di toString().

Implementa esplicitamente (sovrascrivi) toString ()

Forse la considerazione più importante relativa al raggiungimento del massimo valore da toString()è fornire loro implementazioni. Sebbene la radice di tutte le gerarchie di classi Java, Object, fornisca un'implementazione toString () disponibile per tutte le classi Java, il comportamento predefinito di questo metodo non è quasi mai utile. Javadoc per Object.toString () spiega cosa viene fornito di default per toString () quando una versione personalizzata non è fornita per una classe:

Il metodo toString per la classe Object restituisce una stringa composta dal nome della classe di cui l'oggetto è un'istanza, il carattere del segno "@" e la rappresentazione esadecimale senza segno del codice hash dell'oggetto. In altre parole, questo metodo restituisce una stringa uguale al valore di: getClass().getName() + '@' + Integer.toHexString(hashCode())

È difficile trovare una situazione in cui sia utile il nome della classe e la rappresentazione esadecimale del codice hash dell'oggetto separato dal segno @. In quasi tutti i casi, è decisamente più utile fornire un custom, esplicito

toString()

implementazione in una classe per sovrascrivere questa versione predefinita.

Javadoc per Object.toString () ci dice anche cosa toString()dovrebbe comportare un'implementazione in generale e fa anche la stessa raccomandazione che sto facendo qui: override toString():

In generale, il  toString metodo restituisce una stringa che "rappresenta testualmente" questo oggetto. Il risultato dovrebbe essere una rappresentazione concisa ma informativa facile da leggere per una persona. Si raccomanda che tutte le sottoclassi sovrascrivano questo metodo.

Ogni volta che scrivo una nuova classe, ci sono diversi metodi che considero l'aggiunta come parte dell'atto di creazione di una nuova classe. Questi includono

codice hash()

e

è uguale a (oggetto)

se è appropriato. Tuttavia, secondo la mia esperienza e secondo me, l'attuazione esplicita

toString()

è sempre appropriato.

Se la "raccomandazione" Javadoc secondo cui "tutte le sottoclassi sovrascrivono questo metodo" non è sufficiente (quindi non presumo che la mia raccomandazione sia neanche) per giustificare a uno sviluppatore Java l'importanza e il valore di un toString()metodo esplicito , allora consiglio di rivedere Josh L'elemento Java efficace di Bloch "Sovrascrive sempre toString" per ulteriori informazioni sull'importanza dell'implementazione toString(). È mia opinione che tutti gli sviluppatori Java dovrebbero possedere una copia di Effective Java , ma fortunatamente il capitolo con questo elemento toString()è disponibile per coloro che non ne possiedono una copia: Metodi comuni a tutti gli oggetti.

Mantieni / Aggiorna aString ()

È frustrante chiamare in modo esplicito o implicito un oggetto toString()in un'istruzione di log o in un altro strumento diagnostico e ricevere la restituzione del nome della classe predefinito e del codice hash esadecimale dell'oggetto invece di qualcosa di più utile e leggibile. È quasi altrettanto frustrante avere toString()un'implementazione incompleta che non include parti significative delle caratteristiche e dello stato correnti dell'oggetto. Cerco di essere abbastanza disciplinato e di generare e seguire l'abitudine di rivedere sempre l' toString()implementazione insieme a rivedere le equals(Object)e le hashCode()implementazioni di qualsiasi classe su cui lavoro che sia nuova per me o ogni volta che aggiungo o modifico gli attributi di una classe.

Solo i fatti (ma tutti / quasi tutti!)

Nel capitolo di Effective Javaaccennato in precedenza, Bloch scrive: "Quando pratico, il metodo toString dovrebbe restituire tutte le informazioni interessanti contenute nell'oggetto." Può essere doloroso e noioso aggiungere tutti gli attributi di una classe ricca di attributi alla sua implementazione toString, ma il valore per coloro che tentano di eseguire il debug e diagnosticare i problemi relativi a quella classe varrà lo sforzo. In genere mi sforzo di avere tutti gli attributi significativi non nulli della mia istanza nella rappresentazione String generata (e talvolta includo il fatto che alcuni attributi sono nulli). In genere aggiungo anche un testo di identificazione minimo per gli attributi. Per molti versi è più un'arte che una scienza, ma cerco di includere abbastanza testo per differenziare gli attributi senza colpire i futuri sviluppatori con troppi dettagli. La cosa più importante per me è ottenere gli attributi 'valori e qualche tipo di chiave di identificazione in atto.

Conosci il tuo pubblico

Uno degli errori più comuni che ho visto commettere agli sviluppatori Java non principianti toString()è dimenticare cosa ea chi toString()è tipicamente destinato. In generale, toString()è uno strumento di diagnostica e debug che semplifica la registrazione dei dettagli su una particolare istanza in un determinato momento per il successivo debug e diagnostica. In genere è un errore avere interfacce utente che visualizzano rappresentazioni di stringa generate da toString()o per prendere decisioni logiche basate su una toString()rappresentazione (infatti, prendere decisioni logiche su qualsiasi stringa è fragile!). Ho visto sviluppatori ben intenzionati toString()utilizzare un formato XML di ritorno da utilizzare in altri aspetti del codice compatibili con XML. Un altro errore significativo è imporre ai client di analizzare la stringa restituitatoString()al fine di accedere programmaticamente ai membri dei dati. Probabilmente è meglio fornire un metodo getter / accessor pubblico piuttosto che fare affidamento sul toString()non cambiare mai. Tutti questi sono errori perché questi approcci dimenticano l'intenzione di toString()un'implementazione. Ciò è particolarmente insidioso se lo sviluppatore rimuove caratteristiche importanti dal toString()metodo (vedere l'ultimo elemento) per migliorarlo su un'interfaccia utente.

Mi piace che le toString()implementazioni abbiano tutti i dettagli pertinenti e forniscano una formattazione minima per rendere questi dettagli più appetibili. Questa formattazione potrebbe includere caratteri di nuova riga selezionati con giudizio [ System.getProperty("line.seperator");] e tabulazioni, due punti, punti e virgola, ecc. Non investo la stessa quantità di tempo che vorrei in un risultato presentato a un utente finale del software, ma ci provo per rendere la formattazione abbastanza piacevole da essere più leggibile. Cerco di implementare toString()metodi che non siano eccessivamente complicati o costosi da mantenere, ma che forniscano una formattazione molto semplice. Cerco di trattare i futuri manutentori del mio codice come vorrei essere trattato da sviluppatori di cui un giorno manterrò il codice.

Nel suo articolo toString()sull'implementazione, Bloch afferma che uno sviluppatore dovrebbe scegliere se avere o meno il toString()reso in un formato specifico. Se è previsto un formato specifico, ciò dovrebbe essere documentato nei commenti Javadoc e Bloch consiglia inoltre di fornire un inizializzatore statico che possa restituire l'oggetto alle sue caratteristiche di istanza in base a una stringa generata da toString(). Sono d'accordo con tutto questo, ma credo che questo sia più un problema di quanto la maggior parte degli sviluppatori sia disposta a sopportare. Bloch sottolinea anche che qualsiasi modifica a questo formato nelle versioni future causerà dolore e angoscia alle persone che dipendono da esso (motivo per cui non credo sia una buona idea che la logica dipenda da un toString()output). Con una disciplina significativa per scrivere e mantenere la documentazione appropriata, con un formato predefinito per un filetoString()potrebbe essere plausibile. Tuttavia, mi sembra un problema e meglio creare semplicemente un metodo nuovo e separato per tali usi e lasciare il libero toString().

Nessun effetto collaterale tollerato

Per quanto importante sia l' toString()implementazione, è generalmente inaccettabile (e certamente considerato una cattiva forma) avere la chiamata esplicita o implicita della toString()logica dell'impatto o portare a eccezioni o problemi logici. L'autore di un toString()metodo dovrebbe fare attenzione a garantire che i riferimenti siano controllati per null prima di accedervi per evitare una NullPointerException. Molte delle tattiche che ho descritto nel post Effective Java NullPointerException Handling possono essere utilizzate toString()nell'implementazione. Ad esempio, String.valueOf (Object) fornisce un meccanismo semplice per la sicurezza nulla su attributi di origine discutibile.

Allo stesso modo è importante che lo toString()sviluppatore controlli le dimensioni dell'array e di altre dimensioni della raccolta prima di tentare di accedere agli elementi al di fuori di quella raccolta. In particolare, è fin troppo facile imbattersi in una StringIndexOutOfBoundsException quando si tenta di manipolare i valori String con String.substring.

Poiché l' toString()implementazione di un oggetto può essere facilmente richiamata senza che lo sviluppatore se ne renda coscienziosamente conto, questo consiglio per assicurarsi che non generi eccezioni o esegua la logica (in particolare la logica che cambia lo stato) è particolarmente importante. L'ultima cosa che chiunque vuole è che l'atto di registrare lo stato corrente di un'istanza porti a un'eccezione o a un cambiamento di stato e comportamento. Un toString()attuazione dovrebbe essere effettivamente un'operazione di sola lettura in quale stato oggetto viene letto per generare una stringa di ritorno. Se nel processo vengono modificati degli attributi, è probabile che accadano cose brutte in momenti imprevedibili.

È mia posizione che toString()un'implementazione dovrebbe includere solo lo stato nella stringa generata che è accessibile nello stesso spazio di processo al momento della sua generazione. Per me, non è difendibile avere toString()un'implementazione che accede ai servizi remoti per creare la stringa di un'istanza. Forse un po 'meno ovvio è che un'istanza non dovrebbe popolare gli attributi dei dati perché è toString()stata chiamata. L' toString()implementazione dovrebbe riferire solo su come stanno le cose nell'istanza corrente e non su come potrebbero essere o saranno in futuro se si verificano determinati scenari diversi o se le cose vengono caricate. Per essere efficaci nel debug e nella diagnostica, è toString()necessario mostrare come sono le condizioni e non come potrebbero essere.

La formattazione semplice è molto apprezzata

Come descritto sopra, un uso giudizioso di separatori di riga e tabulazioni può essere utile per rendere più appetibili istanze lunghe e complesse quando vengono generate in formato String. Ci sono altri "trucchi" che possono rendere le cose più belle. Non solo String.valueOf(Object)fornisce una certa nullprotezione, ma si presenta anche nullcome String "null" (che spesso è la rappresentazione preferita di null in una String generata da toString (). Arrays.toString (Object) è utile per rappresentare facilmente array come String ( vedere il mio post Stringifying Java Arrays per ulteriori dettagli).

Includere il nome della classe nella rappresentazione toString

Come descritto sopra, l'implementazione predefinita di toString()fornisce il nome della classe come parte della rappresentazione dell'istanza. Quando lo sovrascriviamo esplicitamente, potenzialmente perdiamo il nome della classe. Questo non è un grosso problema in genere se si registra la stringa di un'istanza perché il framework di registrazione includerà il nome della classe. Tuttavia, preferisco essere al sicuro e avere sempre il nome della classe disponibile. Non mi interessa mantenere la rappresentazione del codice hash esadecimale dal valore predefinito toString(), ma il nome della classe può essere utile. Un buon esempio di questo è Throwable.toString (). Preferisco usare quel metodo piuttosto che getMessage o getLocalizedMessage perché il primo ( toString()) include il Throwablenome della classe di mentre gli ultimi due metodi no.

toString () Alternative

Al momento non abbiamo questo (almeno non un approccio standard), ma si è parlato di una classe di oggetti in Java che farebbe molto per preparare in modo sicuro e utile rappresentazioni String di vari oggetti, strutture di dati e raccolte. Non ho sentito parlare di alcun progresso recente in JDK7 su questa classe. toString()Sarebbe utile una classe standard nel JDK che fornisse una rappresentazione String degli oggetti anche quando le definizioni di classe degli oggetti non fornissero un esplicito .

Apache Commons ToStringBuilder può essere la soluzione più popolare per creare implementazioni toString () sicure con alcuni controlli di formattazione di base. Ho già scritto in un blog su ToStringBuilder e ci sono numerose altre risorse online sull'uso di ToStringBuilder.

Il suggerimento tecnico sulla tecnologia Java di Glen McCluskey "Writing toString Methods" fornisce ulteriori dettagli su come scrivere un buon metodo toString (). In uno dei commenti del lettore, Giovanni Pelosi afferma una preferenza per delegare la produzione di una rappresentazione di stringa di un'istanza all'interno di gerarchie di ereditarietà dalla toString()a una classe delegata creata a tale scopo.

Conclusione

Penso che la maggior parte degli sviluppatori Java riconosca il valore di buone toString()implementazioni. Sfortunatamente, queste implementazioni non sono sempre così buone o utili come potrebbero essere. In questo post ho cercato di delineare alcune considerazioni per migliorare le toString()implementazioni. Sebbene un toString()metodo non abbia (o almeno non dovrebbe) influire sulla logica come può fare un metodo equals(Object)o hashCode(), può migliorare il debug e l'efficienza diagnostica. Meno tempo speso per capire qual è lo stato dell'oggetto significa più tempo per risolvere il problema, passare a sfide più interessanti e soddisfare più esigenze del cliente.

Questa storia, "Considerazioni su Java toString ()" è stata originariamente pubblicata da JavaWorld.