Съображения за Java toString ()

Дори начинаещите разработчици на Java са наясно с полезността на метода Object.toString (), който е достъпен за всички екземпляри на класове Java и може да бъде заменен, за да предостави полезни подробности относно всеки конкретен екземпляр на клас Java. За съжаление, дори опитни разработчици на Java понякога не се възползват напълно от тази мощна функция на Java по различни причини. В тази публикация в блога разглеждам смирената Java toString()и описвам лесни стъпки, които човек може да предприеме, за да подобри утилитаризма на toString().

Изрично внедряване (Замяна) toString ()

Може би най-важното съображение, свързано с постигането на максимална стойност, toString()е да се осигури тяхното изпълнение. Въпреки че коренът на всички йерархии на Java класове, Object, осигурява изпълнение на toString (), което е достъпно за всички Java класове, поведението по подразбиране на този метод почти никога не е полезно. Javadoc за Object.toString () обяснява какво се предоставя по подразбиране за toString (), когато не е предоставена персонализирана версия за клас:

Методът toString за клас Object връща низ, състоящ се от името на класа, чийто обект е екземпляр, символа at-sign `@ 'и неподписаното шестнадесетично представяне на хеш кода на обекта. С други думи, този метод връща низ, равен на стойността на: getClass().getName() + '@' + Integer.toHexString(hashCode())

Трудно е да се излезе със ситуация, при която името на класа и шестнадесетичното представяне на хеш кода на обекта да са полезни. В почти всички случаи е значително по-полезно да се предостави персонализиран, изричен

toString()

внедряване в клас за замяна на тази версия по подразбиране.

Javadoc за Object.toString () също ни казва какво toString()обикновено трябва да включва внедряването и също дава същата препоръка, която правя тук: override toString():

По принцип  toString методът връща низ, който "представлява текстово" този обект. Резултатът трябва да бъде кратко, но информативно представяне, което човек да чете лесно. Препоръчително е всички подкласове да отменят този метод.

Винаги, когато пиша нов клас, има няколко метода, които смятам за добавяне като част от акта за създаване на нов клас. Те включват

хеш код()

и

е равно (обект)

ако е уместно. Въпреки това, според моя опит и според мен, прилагането е изрично

toString()

винаги е подходящо.

Ако "препоръката" на Javadoc, че "всички подкласове отменят този метод", не е достатъчна (тогава не предполагам, че и моята препоръка е), за да оправдае на разработчика на Java важността и стойността на изричен toString()метод, тогава препоръчвам да прегледате Джош Ефективният елемент на Java на Bloch "Винаги заменя toString" за допълнителна информация относно важността на изпълнението toString(). Моето мнение е, че всички разработчици на Java трябва да притежават копие на Ефективна Java , но за щастие главата с този елемент toString()е достъпна за тези, които не притежават копие: Методи, общи за всички обекти.

Поддържане / актуализиране до Totring ()

Разочароващо е изрично или имплицитно да извикате обект toString()в лог оператор или друг инструмент за диагностика и да върнете името на класа по подразбиране и шестнадесетичния хеш код на обекта, а не нещо по-полезно и четливо. Почти толкова разочароващо е да имаш непълно toString()изпълнение, което не включва значителни части от текущите характеристики и състояние на обекта. Опитвам се да бъда достатъчно дисциплиниран и да генерирам и следвам навика винаги да преглеждам toString()изпълнението, заедно с прегледа equals(Object)и hashCode()изпълнението на всеки клас, върху който работя, който е нов за мен или когато добавям или променям атрибутите на клас.

Само фактите (но всички / повечето от тях!)

В главата на Ефективна Javaспоменато по-рано, Bloch пише, "Когато е практично, методът toString трябва да върне цялата интересна информация, съдържаща се в обекта." Може да бъде болезнено и досадно да добавяте всички атрибути на атрибут тежък клас към неговата реализация toString, но стойността за тези, които се опитват да отстраняват грешки и диагностицират проблеми, свързани с този клас, ще си струва усилията. Обикновено се стремя да има всички значими ненулеви атрибути на моя екземпляр в генерираното представяне на String (и понякога включва факта, че някои атрибути са null). Също така обикновено добавям минимален идентифициращ текст за атрибутите. В много отношения това е повече изкуство, отколкото наука, но се опитвам да включа достатъчно текст, за да разгранича атрибутите, без да затъмнявам бъдещите разработчици с твърде много детайли. Най-важното за мен е да получа атрибутите 'стойности и някакъв тип идентифициращ ключ на място.

Познай своята публика

Една от най-често срещаните грешки, които съм виждал разработчици на Java за начинаещи, по отношение на това toString()е забравянето за какво и за кого toString()обикновено е предназначен. Като цяло toString()е инструмент за диагностика и отстраняване на грешки, който улеснява регистрирането на подробности за конкретен екземпляр в определен момент за по-късно отстраняване на грешки и диагностика. Обикновено е грешка потребителските интерфейси да показват String представления, генерирани от toString()или да се вземат логически решения въз основа на toString()представяне (всъщност вземането на логически решения за всеки String е крехко!). Виждал съм, че добронамерените разработчици toString()връщат XML формат за използване в някой друг XML аспект на кода. Друга съществена грешка е да принудите клиентите да анализират низа, върнат отtoString()за програмен достъп до членовете на данните. Вероятно е по-добре да предоставите публичен метод за получаване / достъп, отколкото да разчитате на toString()никога непроменящите се. Всичко това са грешки, защото тези подходи забравят намерението за toString()изпълнение. Това е особено коварно, ако разработчикът премахне важни характеристики от toString()метода (вижте последния елемент), за да изглежда по-добре в потребителския интерфейс.

Харесва ми toString()внедряванията да имат всички подходящи детайли и да осигуряват минимално форматиране, за да направят тези детайли по-вкусни. Това форматиране може да включва разумно подбрани символи от нов ред [ System.getProperty("line.seperator");] и раздели, двоеточие, точка и запетая и т.н. Не инвестирам толкова време, колкото бих получил в резултат, представен на краен потребител на софтуера, но се опитвам за да направи форматирането достатъчно хубаво, за да бъде по-четливо. Опитвам се да внедря toString()методи, които не са прекалено сложни или скъпи за поддръжка, но осигуряват много просто форматиране. Опитвам се да се отнасям с бъдещите поддръжници на моя код, както бих искал да бъдат третирани от разработчици, чийто код един ден ще поддържам.

В статията си относно toString()изпълнението Bloch заявява, че разработчикът трябва да избере дали toString()връщането да има определен формат или не . Ако специфичен формат е предназначен, че трябва да бъдат документирани в коментарите на Javadoc и Блок освен това препоръчва статичен инициализатор се при условие, че може да се върне на обекта на своите характеристики например на базата на String, генерирани от toString(). Съгласен съм с всичко това, но вярвам, че това е по-голям проблем, отколкото повечето разработчици са готови да предприемат. Блох също така посочва, че всякакви промени в този формат в бъдещи издания ще причинят болка и безпокойство на хората в зависимост от него (поради което не мисля, че е добра идея логиката да зависи от toString()резултата). Със значителна дисциплина да пишете и поддържате подходяща документация, като имате предварително определен формат за atoString()може да е правдоподобно. Обаче ми се струва проблем и по-добре просто да създам нов и отделен метод за такива употреби и да оставя toString()необременен.

Без странични ефекти

Колкото и важно да е toString()изпълнението, обикновено е неприемливо (и със сигурност се счита за лоша форма) да има изрично или неявно извикване на toString()логиката на въздействието или да води до изключения или логически проблеми. Авторът на toString()метод трябва да бъде внимателен, за да гарантира, че референциите се проверяват за нула преди достъп до тях, за да се избегне NullPointerException. Много от тактиките, които описах в пост Ефективното обработване на Java NullPointerException, могат да бъдат използвани при toString()изпълнението. Например String.valueOf (Object) осигурява лесен механизъм за нулева безопасност на атрибути със съмнителен произход.

По същия начин е важно toString()разработчикът да провери размерите на масивите и други размери на колекцията, преди да се опита да осъществи достъп до елементи извън тази колекция. По-специално е твърде лесно да се натъкнете на StringIndexOutOfBoundsException, когато се опитвате да манипулирате стойностите на String със String.substring.

Тъй като toString()изпълнението на обект може лесно да бъде извикано, без разработчикът да го осъзнае добросъвестно, този съвет, за да се уверите, че не изхвърля изключения или не изпълнява логика (особено логика, променяща състоянието), е особено важен. Последното нещо, което някой иска, е актът на регистриране на текущото състояние на даден екземпляр да доведе до изключение или промяна в състоянието и поведението. Едно toString()изпълнение трябва ефективно да бъде операция само за четене, при която състоянието на обекта се чете, за да се генерира низ за връщане. Ако някакви атрибути се променят в процеса, лошите неща вероятно ще се случат в непредсказуеми моменти.

Моята позиция е, че toString()изпълнението трябва да включва само състояние в генерирания низ, което е достъпно в същото пространство на процеса по време на неговото генериране. За мен не е защитимо да имам toString()реализация с достъп до отдалечени услуги за изграждане на String на екземпляр. Може би малко по-малко очевидно е, че даден екземпляр не трябва да попълва атрибутите на данните, защото toString()е бил извикан. В toString()изпълнението, следва да докладват само как стоят нещата в текущата инстанция, а не за това как те могат да бъдат или ще бъдат в бъдеще, ако се появят някои различни сценарии или ако нещата са заредени. За да бъдат ефективни при отстраняване на грешки и диагностика, toString()трябва да се покаже как са условията, а не как биха могли да бъдат.

Лесното форматиране е искрено оценено

Както е описано по-горе, разумно използване на разделители на редове и раздели може да бъде полезно, за да направи дългите и сложни екземпляри по-вкусни, когато се генерират във формат String. Има и други „трикове“, които могат да направят нещата по-хубави. Не само String.valueOf(Object)осигурява известна nullзащита, но също така се представя nullкато String "null" (което често е предпочитаното представяне на null в String, генериран от toString (). Arrays.toString (Object) е полезен за лесно представяне на масиви като Strings ( вижте публикацията ми Stringifying Java Arrays за допълнителни подробности).

Включете името на класа в представяне на toString

Както е описано по-горе, изпълнението по подразбиране на toString()предоставя името на класа като част от представянето на екземпляра. Когато изрично заменим това, потенциално губим името на този клас. Обикновено това не е голяма работа, ако се регистрира низ на екземпляр, защото рамката за регистриране ще включва име на клас. Предпочитам обаче да съм на сигурно място и винаги да имам на разположение името на класа. Не ме интересува запазването на шестнадесетичното представяне на хеш код по подразбиране toString(), но името на класа може да бъде полезно. Добър пример за това е Throwable.toString (). Предпочитам да използвам този метод, вместо getMessage или getLocalizedMessage, защото първият ( toString()) наистина включва Throwableимето на класа, докато последните два метода не.

toString () Алтернативи

Понастоящем нямаме това (поне не стандартен подход), но се заговори за клас Objects в Java, който би продължил много безопасно и полезно да подготвя String представления на различни обекти, структури от данни и колекции. Не съм чувал за някакъв скорошен напредък в JDK7 в този клас. toString()Полезен би бил стандартен клас в JDK, който предоставя String представяне на обекти, дори когато дефинициите на класовете на обектите не предоставят изрично .

Apache Commons ToStringBuilder може да бъде най-популярното решение за изграждане на безопасни реализации на toString () с някои основни контроли за форматиране. Вече съм писал блог в ToStringBuilder и има много други онлайн ресурси относно използването на ToStringBuilder.

Техническият съвет на Glen McCluskey за технологията Java "Методи за писане в toString" предоставя допълнителни подробности за това как да напишете добър метод toString (). В един от коментарите на читателя Джовани Пелоси заявява предпочитание за делегиране на производството на низово представяне на екземпляр в йерархиите на наследяване от toString()клас на делегат, създаден за тази цел.

Заключение

Мисля, че повечето разработчици на Java признават стойността на добрите toString()внедрения. За съжаление тези внедрения не винаги са толкова добри или полезни, колкото биха могли да бъдат. В тази публикация се опитах да очертая някои съображения за подобряване на toString()изпълнението. Въпреки че toString()методът няма (или поне не би трябвало) да влияе на логиката, както може equals(Object)или hashCode()методът, той може да подобри отстраняването на грешки и ефективността на диагностиката. По-малкото време, прекарано в установяване на състоянието на обекта, означава повече време за решаване на проблема, преминаване към по-интересни предизвикателства и задоволяване на повече нужди на клиента.

Тази история „Съображения за Java toString ()“ първоначално е публикувана от JavaWorld.