JE_09: Vždy překrývejte toString

Třebaže java.lang.Object poskytuje implementaci metody toString, vrácený řetězec obvykle neodpovídá tomu, co by chtěl uživatel vaší třídy vidět. Skládá se z názvu třídy, za nímž následuje znak @ a šestnáctková reprezentace hešovacího kódu bez znaménka, tedy například PhoneNumber@163b91. Obecný kontrakt metody toString říká, že vrácený řetězec by měl být "stručnou a informativní reprezentací, které uživatel snadno porozumí". Kontrakt metody toString dále říká: "Doporučuje se, aby všechny podtřídy tuto metodu překrývaly". To je opravdu dobrá rada.

Třebaže to není tak důležité, jako vyhovět kontraktům metod equals a hashCode (rada 7, rada 8), po poskytnutí dobré implementace toString se bude vaše třída mnohem příjemněji používat. Metoda toString se automaticky volá, když se váš objekt předává println, operátoru spojování řetězců (+), nebo ve verzi 1.4 metodě assert. Poskytnete-li dobrou metodu toString, pak je vytváření užitečných diagnostických zpráv velmi jednoduché:

	System.out.println("Neúspešně spojení: "+ phoneNumber);

Programátoři budou generovat diagnostické zprávy títmo způsobem, ať už metodu toString přepíšete nebo ne, ale tyto zprávy nebudou čitelné, pokud tak neučiníte. Výhody získané poskytnutím dobré metody toString přesahují instance dané třídy a přecházejí až na objekty obsahující odkazy na tyto instance, zejména kolekce. Co byste radji viděli při tisku mapy, {Jenny=PhoneNumber@163b91} nebo {Jenny=(408) 867-5309}?

Je-li to praktické, pak by metoda toString měla vrátit všechny zajímavé informace obsažené v objektu, jako je tomu právě v ukázeném příkladu telefonního čísla. To je nepraktické, když je daný objekt velký nebo obsahuje-li stav, který nelze dobře převést na řetězcovou reprezentaci. V atkových případech by měla metoda toString vrátit nějaký přehled, například Zlaté stránky (1487536 výpisů) nebo Vlákno[main, 5, main]. V ideálním případě by měl takový řetězec vysvětlovat sám sebe (tento požadavek nenapl�uje například Vlákno).

Při implementování metody toString budete muset učinit jedno důležité rozhodnutí: zda specifikovat formát návratové hodnoty v dokumentaci. To se doporučuje v případě hodnotových tříd, jako jsou telefonní čísla nebo matice. Výhodou zadání formátu je skutečnost, že slouží jako standardní, jasná a uživatelem čitelná reprezentace daného objektu. Tato reprezentace může být použita jako vstup nebo výstup a ve stálých datových objektech čitelných uživateli, jako jsou například dokumenty XML. Zadáte-li formát, obvykle je vhodné poskytnout také odpovídající konstruktor String (nebo statickou tovární metodu, viz. rada 1), aby programátoři mohli snadno převádět objekt na jeho řetězcovou reprezentaci a naopak. Tento přístup volí mnoho hodnotových tříd v knihovnách platformy Java, mezi které patří BigInteger, BigDecimal a většina obalových tříd primitivních datových typů.

Nevýhodou zadání formátu návratové hodnoty toString je to, že se už nemůže změnit - pokud se tedy bude vaše třída rozsáhle používat. Programátoři budou psát kód převádějící reprezentaci, genrující ji a vkládající do stálých dat. Změníte-li reprezentaci v nějaké další verzi, narušíte jejich kód a data a oni budou úpět. Když formát nespecifikujete, zachováte si možnost v další verzi třídy přidat informace nebo tento formát jinak zlepšit.

Ať už se rozhodnete formát specifikovat nebo ne, měli byste jasně zdokumentovat své záměry. Pokud formát zadáte, uči�te tak přesně. Zde máme například metodu toString patřící třídě PhoneNumber z rady 8:

	
	/**
	 * Vrací řetězcovou reprezentaci daného telefonního čísla.
	 * �etězec se skládá ze čtrnácti znaků, jejichž formát je
	 * "(XXX) YYY-ZZZZ", kde XXX představuje kód oblasti, YYY
	 * je ústředna a ZZZZ je rozšíření (každé z velkých písmen 
	 * představuje jednu desítkovou číslici).
	 *
	 * Je-li některá ze tří částí telefonního čísla příliš malá
	 * a daný atribut nevyplní, pak se na jeho začátek umístí nuly.
	 * Pokud je například hodnota rozšíření 123, budou mít poslední
	 * čtyři znaky řetězcové reprezentace tvar "0123".
	 *
	 * Všimněte si jedné mezery oddělující ukončovací závorku
	 * za kódem oblasti od první číslice ústředny.
	 */
	public String toString() {
		return "("+ toPaddedstring(areaCode, 3) +") "+
			toPaddedString(exchange, 3) +"-"+
			toPaddedString(extension, 4);
	}

	
	/**
	 * Převádí typ int na řetězec zadané délky s doplněním
	 * úvodních nul. Předpokládá, že i >= 0,
	 * 1 <= length <= 10 a Integer.toString(i) <= length.
	 */
	private static String toPaddedString(int i, int length) {
		String s = Integer.toString(i);
		return ZEROS[length - s.length()] + s;
	}

	private static String[] ZEROS = {
		"", "0", "00", "000", 
		"0000", "00000", "000000",
		"0000000", "0000000", "000000000"};

Rozhodnete-li se nzadávat formát, pak by měl komentář v dokumentaci vypadat nějak takto:

	
	/**
	 * Vrací krátky popis tohoto nápoje. Podrobnosti reprezentace
	 * nejsou zadány a mohou se měnit, za typyickou lze však
	 * považovat tu následující:
	 * "[Nápoj #9: typ=láska, vůně=terpentýn, vzhled=inkoust]"
	 */
	public String toString() {
		. . .
	}

Když si programátoři přečtou tento komentář pak vytvoří nějaký kód nebo trvalá dat závisejíc na detailech formátu, pak po jeho změně budou moci obvi�ovat jen sami sebe.

Ať už formát zadáte nebo ne, vždy je vhodné poskytnout programový přístup ke všem informacím, obsaženým v hodnotě vrácené metodou toString. Například třída PhoneNumber by měla obsahovat přístup ke kódu oblasti, ústředně a rozšíření. Pokud to nezajistíte, pak nutíte programátory, kteří tyto informace potřebují, k analyzování řetězce. Nejenže se tím snižuje výkonnost a programátoři musejí vykonávat zbytečné činnosti,a le navíce je celý proces náchylný k chybám a jeho výsledkem jsou křehké systémy, které se při změně formátu zhroutí. Pokud nezajístíte přímý přístup, pak měníte řetězcový formát de facto na API, třebaže jste řekli, že se může změnit.