Programmering

Java toString () Overvejelser

Selv begyndende Java-udviklere er opmærksomme på nytten af ​​Object.toString () -metoden, der er tilgængelig for alle forekomster af Java-klasser og kan tilsidesættes for at give nyttige oplysninger om enhver bestemt forekomst af en Java-klasse. Desværre udnytter selv erfarne Java-udviklere lejlighedsvis ikke fuldt ud denne kraftfulde Java-funktion af forskellige årsager. I dette blogindlæg ser jeg på den ydmyge Java toString () og beskrive nemme trin man kan tage for at forbedre utilitarismen af toString ().

Implicit implementere (tilsidesætte) toString ()

Måske den vigtigste overvejelse relateret til at opnå maksimal værdi fra toString () er at give implementeringer af dem. Selvom roden til alle Java-klassehierarkier, Object, giver en toString () -implementering, der er tilgængelig for alle Java-klasser, er denne metodes standardadfærd næsten aldrig nyttig. Javadoc for Object.toString () forklarer, hvad der som standard leveres til toString (), når en tilpasset version ikke leveres til en klasse:

Metoden toString for klasse Objekt returnerer en streng bestående af navnet på den klasse, som objektet er en forekomst af, at-tegntegnet `@ 'og den usignerede hexadecimale repræsentation af objektets hash-kode. Med andre ord returnerer denne metode en streng svarende til værdien af:getClass (). getName () + '@' + Integer.toHexString (hashCode ())

Det er vanskeligt at komme med en situation, hvor navnet på klassen og den hexadecimale repræsentation af objektets hash-kode adskilt af @ -tegnet er nyttigt. I næsten alle tilfælde er det betydeligt mere nyttigt at levere en brugerdefineret, eksplicit

toString ()

implementering i en klasse for at tilsidesætte denne standardversion.

Javadoc for Object.toString () fortæller os også, hvad en toString () implementering skal generelt medføre og giver også den samme anbefaling, som jeg her: tilsidesætte toString ():

Generelt er dettoString metode returnerer en streng, der "tekstmæssigt repræsenterer" dette objekt. Resultatet skal være en kortfattet, men informativ gengivelse, der er let for en person at læse. Det anbefales, at alle underklasser tilsidesætter denne metode.

Hver gang jeg skriver en ny klasse, er der flere metoder, jeg overvejer at tilføje som en del af handlingen med oprettelse af en ny klasse. Disse inkluderer

hashCode ()

og

er lig med (Objekt)

hvis passende. Imidlertid efter min erfaring og efter min mening implementering eksplicit

toString ()

er altid passende.

Hvis Javadocs "anbefaling", at "alle underklasser tilsidesætter denne metode" ikke er nok (så antager jeg ikke, at min anbefaling heller er) til at retfærdiggøre over for en Java-udvikler vigtigheden og værdien af ​​en eksplicit toString () metode, så anbefaler jeg at gennemgå Josh Blochs effektive Java-emne "Always Override toString" for yderligere baggrund om vigtigheden af ​​at implementere toString (). Det er min opfattelse, at alle Java-udviklere skal eje en kopi af Effektiv Java, men heldigvis kapitlet med denne vare på toString () er tilgængelig for dem, der ikke ejer en kopi: Metoder, der er fælles for alle objekter.

Vedligehold / opdater toString ()

Det er frustrerende at eksplicit eller implicit kalde et objekt toString () i en logerklæring eller et andet diagnostisk værktøj og få standardklassenavnet og objektets hexidecimale hash-kode returneret snarere end noget mere nyttigt og læsbart. Det er næsten lige så frustrerende at have en ufuldstændig toString () implementering, der ikke inkluderer væsentlige dele af objektets aktuelle egenskaber og tilstand. Jeg prøver at være disciplineret nok og generere og følge vanen med altid at gennemgå toString () implementering sammen med gennemgang af er lig med (Objekt) og hashCode () implementeringer af enhver klasse, jeg arbejder på, er nye for mig, eller når jeg tilføjer eller ændrer attributterne for en klasse.

Bare fakta (men alle / det meste af dem!)

I kapitlet om Effektiv Java tidligere nævnt skriver Bloch: "Når praktisk, skal toString-metoden returnere alle de interessante oplysninger indeholdt i objektet." Det kan være smertefuldt og kedeligt at tilføje alle attributter fra en attribut-tung klasse til implementeringen af ​​toString, men værdien for dem, der forsøger at debugge og diagnosticere problemer relateret til den klasse, er det værd at gøre. Jeg stræber typisk efter at have alle vigtige ikke-nul-attributter for min forekomst i den genererede strengrepræsentation (og nogle gange inkluderer det faktum, at nogle attributter er nul). Jeg tilføjer typisk også minimal identificerende tekst til attributterne. Det er på mange måder mere en kunst end en videnskab, men jeg prøver at inkludere nok tekst til at differentiere attributter uden at udslette fremtidige udviklere med for mange detaljer. Det vigtigste for mig er at få attributternes værdier og en slags identificeringsnøgle på plads.

Kend dit publikum

En af de mest almindelige fejl, jeg har set ikke-begyndere Java-udviklere laver med hensyn til toString () glemmer hvad og hvem toString () er typisk beregnet til. Generelt, toString () er et diagnostisk og fejlretningsværktøj, der gør det let at logge detaljer om en bestemt forekomst på et bestemt tidspunkt til senere fejlretning og diagnosticering. Det er typisk en fejl at få brugergrænseflader til at vise strengrepræsentationer genereret af toString () eller at træffe logiske beslutninger baseret på en toString () repræsentation (faktisk er det skrøbeligt at træffe logiske beslutninger på enhver streng!). Jeg har set velmenende udviklere have toString () returnere XML-format til brug i et andet XML-venligt aspekt af kode. En anden væsentlig fejl er at tvinge klienter til at analysere den streng, der er returneret fra toString () for programmatisk at få adgang til datamedlemmer. Det er sandsynligvis bedre at give en offentlig getter / accessor-metode end at stole på toString () ændrer sig aldrig. Alt dette er fejl, fordi disse tilgange glemmer hensigten med en toString () implementering. Dette er især snigende, hvis udvikleren fjerner vigtige egenskaber fra toString () metode (se sidste punkt) for at få det til at se bedre ud på en brugergrænseflade.

jeg kan lide toString () implementeringer for at have alle relevante detaljer og for at give minimal formatering for at gøre disse detaljer mere velsmagende. Denne formatering kan omfatte omhyggeligt valgte nye linjetegn [System.getProperty ("line.seperator");] og faner, kolon, semikoloner osv. Jeg investerer ikke den samme tid, som jeg ville i et resultat, der blev præsenteret for en slutbruger af softwaren, men jeg prøver at gøre formateringen tilstrækkelig til at være mere læselig. Jeg prøver at gennemføre toString () metoder, der ikke er for komplicerede eller dyre at vedligeholde, men som giver nogle meget enkle formateringer. Jeg prøver at behandle fremtidige vedligeholdere af min kode, da jeg gerne vil blive behandlet af udviklere, hvis kode jeg en dag vil vedligeholde.

I hans punkt på toString () implementering, siger Bloch, at en udvikler skal vælge, om han vil have toString () returnere et bestemt format. Hvis et bestemt format er beregnet, skal det dokumenteres i Javadoc-kommentarerne, og Bloch anbefaler yderligere, at der tilvejebringes en statisk initialisering, der kan returnere objektet til dets forekomstskarakteristika baseret på en streng genereret af toString (). Jeg er enig i alt dette, men jeg tror, ​​det er mere besvær end de fleste udviklere er villige til at gå. Bloch påpeger også, at eventuelle ændringer i dette format i fremtidige udgivelser vil forårsage smerte og angst for folk afhængigt af det (det er derfor, jeg synes ikke det er en god ide at have logik afhænger af en toString () produktion). Med betydelig disciplin til at skrive og vedligeholde passende dokumentation med et foruddefineret format til en toString () kan være plausibelt. Det ser imidlertid ud til at være besvær for mig og bedre bare at oprette en ny og separat metode til sådanne anvendelser og forlade toString () ubehæftet.

Ingen tolererede bivirkninger

Lige så vigtigt som toString () implementering er, det er generelt uacceptabelt (og bestemt betragtes som dårlig form) at have den eksplicitte eller implicitte kaldelse af toString () påvirke logik eller føre til undtagelser eller logiske problemer. Forfatteren af ​​en toString () metoden skal være omhyggelig med at sikre, at referencer kontrolleres for null, inden de får adgang til dem for at undgå en NullPointerException. Mange af de taktikker, jeg beskrev i posten Effektiv Java NullPointerException Handling, kan bruges i toString () implementering. For eksempel giver String.valueOf (Object) en nem mekanisme til nul sikkerhed for attributter af tvivlsom oprindelse.

Det er ligeledes vigtigt for toString () udvikler til at kontrollere arraystørrelser og andre samlingstørrelser, før han prøver at få adgang til elementer uden for denne samling. Især er det alt for let at løbe ind i en StringIndexOutOfBoundsException, når man prøver at manipulere strengværdier med String.substring.

Fordi et objekt er toString () implementering kan let påberåbes uden at udvikleren samvittighedsfuldt er klar over det, dette råd til at sikre, at det ikke kaster undtagelser eller udfører logik (især tilstandsændrende logik) er særligt vigtigt. Det sidste, som nogen vil have, er at få lov til at logge en instans nuværende tilstand føre til en undtagelse eller ændring i tilstand og adfærd. EN toString () implementering skal effektivt være en skrivebeskyttet operation, hvor objekttilstand læses for at generere en streng til returnering. Hvis nogen attributter ændres i processen, vil dårlige ting sandsynligvis ske på uforudsigelige tidspunkter.

Det er min holdning, at toString () implementering bør kun omfatte tilstand i den genererede streng, der er tilgængelig i det samme procesrum på tidspunktet for dens generering. For mig er det ikke forsvarligt at have en toString () implementering få adgang til fjerntjenester for at opbygge en instanss streng. Måske lidt mindre indlysende er, at en instans ikke skal udfylde dataattributter, fordi toString () blev kaldt. Det toString () implementering bør kun rapportere om, hvordan tingene er i den aktuelle instans, og ikke om, hvordan de kan være eller vil være i fremtiden, hvis visse forskellige scenarier opstår, eller hvis ting indlæses. For at være effektiv i debugging og diagnostik, er toString () skal vise, hvordan forholdene er, og ikke hvordan de kan være.

Enkel formatering værdsættes med venlig hilsen

Som beskrevet ovenfor kan omhyggelig brug af linjeseparatorer og faner være nyttigt til at gøre lange og komplekse forekomster mere velsmagende, når de genereres i strengformat. Der er andre "tricks", der kan gøre tingene pænere. Ikke kun gør det String.valueOf (Object) give nogle nul beskyttelse, men det præsenterer også nul som String "null" (som ofte er den foretrukne repræsentation af null i en toString () - genereret streng. Arrays.toString (Object) er nyttig til let at repræsentere arrays som strenge (se mit indlæg Stringifying Java Arrays for yderligere detaljer).

Inkluder klasse navn i toString repræsentation

Som beskrevet ovenfor er standardimplementeringen af toString () angiver klassens navn som en del af instansens repræsentation. Når vi udtrykkeligt tilsidesætter dette, mister vi potentielt dette klassenavn. Dette er ikke en big deal, typisk hvis logning af en instansstreng, fordi logningsrammen inkluderer klasse navn. Jeg foretrækker dog at være på den sikre side og altid have klassens navn til rådighed. Jeg er ligeglad med at holde den hexadecimale hashkodepræsentation fra standard toString (), men klassenavn kan være nyttigt. Et godt eksempel på dette er Throwable.toString (). Jeg foretrækker at bruge denne metode i stedet for getMessage eller getLocalizedMessage, fordi den tidligere (toString ()) inkluderer også Kan kastesklassens navn, mens de to sidstnævnte metoder ikke gør det.

toString () Alternativer

Vi har i øjeblikket ikke dette (i det mindste ikke en standard tilgang), men der har været tale om en objektsklasse i Java, der ville gå lang tid i retning af sikkert og nyttigt at forberede strengrepræsentationer af forskellige objekter, datastrukturer og samlinger. Jeg har ikke hørt om nogen nylige fremskridt i JDK7 på denne klasse. En standardklasse i JDK, der leverede strengrepræsentation af objekter, selv når objekternes klassedefinitioner ikke gav en eksplicit toString () ville være nyttigt.

Apache Commons ToStringBuilder er muligvis den mest populære løsning til opbygning af sikre toString () -implementeringer med nogle grundlæggende formateringskontroller. Jeg har tidligere blogget på ToStringBuilder, og der er mange andre online ressourcer vedrørende brug af ToStringBuilder.

Glen McCluskeys Java Technology Tech Tip "Writing toString Methods" indeholder yderligere detaljer om, hvordan man skriver en god toString () -metode. I en af ​​læserkommentarerne angiver Giovanni Pelosi en præference for at delegere produktion af en strengrepræsentation af en instans inden for arvshierarkier fra toString () til en delegatklasse bygget til dette formål.

Konklusion

Jeg tror, ​​de fleste Java-udviklere anerkender værdien af ​​godt toString () implementeringer. Desværre er disse implementeringer ikke altid så gode eller nyttige som de kunne være. I dette indlæg har jeg forsøgt at skitsere nogle overvejelser for at forbedre toString () implementeringer. Selvom en toString () metode påvirker ikke (eller i det mindste ikke) logik som en er lig med (Objekt) eller hashCode () metode kan, kan det forbedre fejlretning og diagnostisk effektivitet. Mindre tid brugt på at finde ud af, hvad objektets tilstand er, betyder mere tid til at løse problemet, gå videre til mere interessante udfordringer og tilfredsstille flere kundebehov.

Denne historie, "Java toString () Overvejelser" blev oprindeligt udgivet af JavaWorld.