Programování

Aspekty Java toString ()

Dokonce i začínající vývojáři v prostředí Java jsou si vědomi užitečnosti metody Object.toString (), která je k dispozici pro všechny instance tříd Java a lze ji přepsat, aby poskytla užitečné podrobnosti týkající se jakékoli konkrétní instance třídy Java. Bohužel ani ostřílení vývojáři Java občas tuto výkonnou funkci Java z různých důvodů plně nevyužijí. V tomto příspěvku na blogu se podívám na pokornou Javu toString () a popište snadné kroky, kterými lze zlepšit utilitarismus toString ().

Explicitně implementovat (přepsat) toString ()

Snad nejdůležitější úvaha týkající se dosažení maximální hodnoty z toString () je poskytovat jejich implementace. Ačkoli kořen všech hierarchií tříd Java, Object, poskytuje implementaci toString (), která je k dispozici pro všechny třídy Java, výchozí chování této metody není téměř nikdy užitečné. Javadoc pro Object.toString () vysvětluje, co je ve výchozím nastavení pro toString () poskytováno, když pro třídu není k dispozici vlastní verze:

Metoda toString pro třídu Object vrací řetězec skládající se z názvu třídy, jejíž je objekt instancí, znaku zavináč „@“ a nepodepsané hexadecimální reprezentace hash kódu objektu. Jinými slovy, tato metoda vrací řetězec rovný hodnotě:getClass (). getName () + '@' + Integer.toHexString (hashCode ())

Je obtížné přijít se situací, kdy je užitečný název třídy a hexadecimální reprezentace hash kódu objektu odděleného znakem @. Téměř ve všech případech je podstatně užitečnější poskytnout vlastní, explicitní

toString ()

implementace ve třídě k přepsání této výchozí verze.

Javadoc pro Object.toString () nám také říká, co a toString () implementace by obecně měla zahrnovat a také dělá stejné doporučení, které zde dělám: přepsat toString ():

Obecně platí, žetoString metoda vrací řetězec, který "textově představuje" tento objekt. Výsledkem by mělo být stručné, ale informativní vyjádření, které je pro člověka snadno čitelné. Doporučuje se, aby tuto metodu přepsaly všechny podtřídy.

Kdykoli píšu novou třídu, existuje několik metod, které přidávám jako součást aktu vytváření nové třídy. Tyto zahrnují

hashCode ()

a

rovná se (Objekt)

Pokud je to vhodné. Podle mých zkušeností a podle mého názoru je však implementace výslovná

toString ()

je vždy vhodné.

Pokud „doporučení“ Javadocu, že „všechny podtřídy přepisují tuto metodu“, nestačí (pak nepředpokládám, že moje doporučení je buď), aby vývojáři Java ospravedlnilo význam a hodnotu explicitního toString () Metoda, pak doporučuji zkontrolovat Efektivní položku Java „Vždy přepsat toString“ Josha Blocha, kde najdete další informace o důležitosti implementace toString (). Podle mého názoru by všichni vývojáři Java měli vlastnit kopii Efektivní Java, ale naštěstí kapitola s touto položkou toString () je k dispozici pro ty, kteří nevlastní kopii: Metody společné všem objektům.

Údržba / aktualizace toString ()

Je frustrující explicitně nebo implicitně volat objekt toString () ve výpisu protokolu nebo jiném diagnostickém nástroji a mají vrácen výchozí název třídy a hexidcimální hash kód objektu, spíše než něco užitečnějšího a čitelnějšího. Je téměř stejně frustrující mít nekompletní toString () implementace, která nezahrnuje významné části aktuálních charakteristik a stavu objektu. Snažím se být dostatečně disciplinovaný a generovat a dodržovat zvyk vždy přezkoumávat toString () provádění spolu s přezkoumáním rovná se (Objekt) a hashCode () implementace jakékoli třídy, na které pracuji, je pro mě nová nebo kdykoli přidávám nebo měním atributy třídy.

Jen fakta (ale všechna / většina z nich!)

V kapitole Efektivní Java dříve zmíněný Bloch píše: „Je-li to praktické, měla by metoda toString vrátit všechny zajímavé informace obsažené v objektu.“ Může být bolestivé a zdlouhavé přidávat do implementace toString všechny atributy třídy náročné na atributy, ale hodnota pro ty, kteří se snaží ladit a diagnostikovat problémy související s touto třídou, bude stát za námahu. Obvykle se snažím mít všechny významné nenulové atributy mé instance v generované řetězcové reprezentaci (a někdy zahrnovat skutečnost, že některé atributy jsou null). Také obvykle přidávám minimální identifikační text pro atributy. Je to v mnoha ohledech více umění než věda, ale snažím se zahrnout dostatek textu k odlišení atributů, aniž bych budoucí vývojáře obtěžoval příliš podrobně. Nejdůležitější pro mě je získat hodnoty atributů a nějaký typ identifikačního klíče.

Zná své publikum

Jedna z nejčastějších chyb, se kterými jsem se setkal u vývojářů Java, kteří nezačínají toString () zapomíná, co a kdo toString () je obvykle určen pro. Obecně, toString () je diagnostický a ladicí nástroj, který usnadňuje protokolování podrobností o konkrétní instanci v určitou dobu pro pozdější ladění a diagnostiku. Obvykle je chybou, aby uživatelská rozhraní zobrazovala řetězcové reprezentace generované toString () nebo činit logická rozhodnutí na základě a toString () reprezentace (ve skutečnosti je logické rozhodování o libovolném řetězci křehké!). Viděl jsem dobře míněné vývojáře toString () vrátit formát XML pro použití v nějakém jiném aspektu kódu vhodném pro XML. Další významnou chybou je přinutit klienty analyzovat řetězec, ze kterého se vrátil toString () za účelem programového přístupu k datovým členům. Pravděpodobně je lepší poskytnout veřejnou metodu getter / accessor, než se spoléhat na toString () nikdy se nemění. To vše jsou chyby, protože tyto přístupy zapomínají na záměr a toString () implementace. To je obzvláště zákeřné, pokud vývojář odstraní z aplikace důležité vlastnosti toString () metoda (viz poslední položka), aby to na uživatelském rozhraní vypadalo lépe.

Mám rád toString () implementace, které mají všechny příslušné podrobnosti a poskytují minimální formátování, aby byly tyto podrobnosti chutnější. Toto formátování může obsahovat uvážlivě vybrané nové řádkové znaky [System.getProperty ("line.seperator");] a záložky, dvojtečky, středníky atd. Neinvestuji stejné množství času jako do výsledku prezentovaného koncovému uživateli softwaru, ale snažím se, aby formátování bylo dostatečně pěkné, aby bylo více čitelný. Snažím se implementovat toString () metody, jejichž údržba není příliš složitá nebo nákladná, ale poskytují velmi jednoduché formátování. Snažím se zacházet s budoucími správci mého kódu tak, jak bych chtěl, aby s nimi zacházeli vývojáři, jejichž kód jednoho dne budu udržovat.

V jeho položce na toString () Bloch uvádí, že vývojář by si měl vybrat, zda bude mít toString () vrátit konkrétní formát. Pokud je zamýšlen konkrétní formát, měl by být zdokumentován v komentářích Javadoc a Bloch dále doporučuje, aby byl poskytnut statický inicializátor, který může vrátit objektu jeho vlastnosti instance na základě řetězce vygenerovaného toString (). S tím vším souhlasím, ale věřím, že je to větší problém, než je většina vývojářů ochotna jít. Bloch také poukazuje na to, že jakékoli změny tohoto formátu v budoucích verzích způsobí lidem bolest a úzkost v závislosti na tom (což je důvod, proč si nemyslím, že je dobrý nápad mít logiku závislou na toString () výstup). S významnou disciplínou psát a udržovat příslušnou dokumentaci, s předdefinovaným formátem pro toString () může být věrohodné. Zdá se mi však jako problém a lepší jednoduše vytvořit novou a samostatnou metodu pro takové použití a ponechat toString () nezatížený.

Žádné tolerované vedlejší účinky

Stejně důležité jako toString () implementace je, je obecně nepřijatelné (a rozhodně považováno za špatnou formu) mít explicitní nebo implicitní volání toString () ovlivnit logiku nebo vést k výjimkám nebo logickým problémům. Autor a toString () metoda by měla být opatrná, aby zajistila, že odkazy budou před přístupem zkontrolovány na null, aby se zabránilo NullPointerException. Lze použít mnoho taktik, které jsem popsal v příspěvku Effective Java NullPointerException Handling toString () implementace. Například String.valueOf (Object) poskytuje snadný mechanismus pro nulovou bezpečnost atributů pochybného původu.

Je to stejně důležité pro toString () vývojář, aby před pokusem o přístup k prvkům mimo tuto kolekci zkontroloval velikosti polí a další velikosti kolekce. Zejména je příliš snadné spustit StringIndexOutOfBoundsException při pokusu o manipulaci s hodnotami String pomocí String.substring.

Protože objekt toString () implementaci lze snadno vyvolat bez toho, aby si to vývojář svědomitě uvědomil, je obzvláště důležitá tato rada, která má zajistit, že nebude vyvolávat výjimky nebo provádět logiku (zejména logiku změny stavu) Poslední věc, kterou někdo chce, je, aby akt logování aktuálního stavu instance vedl k výjimce nebo změně stavu a chování. A toString () implementace by měla být účinně operace jen pro čtení, ve které se čte stav objektu, aby se vygeneroval řetězec pro návrat. Pokud se v procesu změní nějaké atributy, pravděpodobně dojde k nepříznivým věcem v nepředvídatelných dobách.

Podle mého názoru a toString () implementace by měla obsahovat pouze stav v generovaném řetězci, který je v době jeho generování přístupný ve stejném prostoru procesu. Pro mě není obhájitelné mít toString () implementace přístup ke vzdáleným službám k vytvoření řetězce instance. Možná o něco méně zřejmé je, že instance by neměla naplňovat datové atributy, protože toString () byl zavolán. The toString () implementace by měla hlásit pouze to, jak jsou věci v aktuální instanci, a nikoli o tom, jak by mohly nebo budou v budoucnu, pokud nastanou určité různé scénáře nebo pokud jsou věci načteny. Aby byly efektivní při ladění a diagnostice, toString () musí ukázat, jaké jsou podmínky a ne jaké by mohly být.

Jednoduché formátování je upřímně oceněno

Jak je popsáno výše, uvážlivé použití oddělovačů řádků a záložek může být užitečné při vytváření delších a složitějších instancí chutnějších při generování ve formátu řetězce. Existují i ​​další „triky“, které mohou udělat věci hezčími. Nejen že ano String.valueOf (objekt) poskytnout některé nula ochranu, ale také představuje nula jako String "null" (což je často preferovaná reprezentace null v řetězci generovaném toString (). Arrays.toString (Object) je užitečné pro snadné reprezentování polí jako Strings (další podrobnosti viz můj příspěvek Stringifying Java Arrays).

Zahrnout název třídy do reprezentace toString

Jak je popsáno výše, výchozí implementace toString () poskytuje název třídy jako součást reprezentace instance. Když to výslovně přepíšeme, potenciálně ztratíme tento název třídy. Nejedná se obvykle o velký problém při protokolování řetězce instance, protože rámec protokolování bude obsahovat název třídy. Dávám však přednost bezpečnosti a vždy mám k dispozici název třídy. Nezajímá mě, jak ponechat hexadecimální reprezentaci hash kódu z výchozí hodnoty toString (), ale název třídy může být užitečný. Dobrým příkladem toho je Throwable.toString (). Dávám přednost použití této metody spíše než getMessage nebo getLocalizedMessage, protože první (toString ()) zahrnuje Vrhacínázev třídy, zatímco poslední dvě metody nikoli.

toString () Alternativy

V současné době to nemáme (alespoň ne standardní přístup), ale hovořilo se o třídě objektů v Javě, která by šla dlouho směrem k bezpečné a užitečné přípravě řetězcových reprezentací různých objektů, datových struktur a sbírek. Neslyšel jsem o žádném nedávném pokroku v JDK7 v této třídě. Standardní třída v JDK, která poskytla řetězcovou reprezentaci objektů, i když definice tříd objektů neposkytly explicitní toString () by bylo užitečné.

Apache Commons ToStringBuilder může být nejpopulárnějším řešením pro vytváření bezpečných implementací ToString () s některými základními ovládacími prvky formátování. Již dříve jsem blogoval na ToStringBuilder a existuje mnoho dalších online zdrojů týkajících se používání ToStringBuilder.

Glen McCluskey's Java Technology Tech Tip "Writing toString Methods" poskytuje další podrobnosti o tom, jak napsat dobrou metodu toString (). V jednom z komentářů čtenáře uvádí Giovanni Pelosi preference pro delegování výroby řetězcové reprezentace instance v hierarchiích dědičnosti z toString () do třídy delegátů vytvořené pro tento účel.

Závěr

Myslím, že většina vývojářů Java uznává hodnotu dobra toString () implementace. Bohužel tyto implementace nejsou vždy tak dobré nebo užitečné, jak by mohly být. V tomto příspěvku jsem se pokusil nastínit několik úvah o zlepšení toString () implementace. I když toString () metoda nebude (nebo alespoň by neměla) ovlivňovat logiku jako rovná se (Objekt) nebo hashCode () metoda může, může zlepšit účinnost ladění a diagnostiky. Méně času stráveného zjišťováním, jaký je stav objektu, znamená více času na vyřešení problému, přechod na zajímavější výzvy a uspokojení dalších potřeb klienta.

Tento příběh „Úvahy Java toString ()“ původně publikoval JavaWorld.

$config[zx-auto] not found$config[zx-overlay] not found