Programmering

Java Tip 99: Oprettelse af toString ()

Udviklere, der arbejder på store projekter, bruger ofte timer på at skrive nyttige toString metoder. Selvom hver klasse ikke får sin egen toString metode, vil hver datacontainerklasse. Tillader hver udvikler at skrive toString hans eller hendes egen måde kan føre til kaos; hver udvikler vil uden tvivl komme med et unikt format. Som et resultat bliver brugen af ​​output under debugging vanskeligere end nødvendigt uden nogen åbenbar fordel. Derfor skal hvert projekt standardisere på et enkelt format til toString metoder og derefter automatisere deres oprettelse.

Automatiser toString

Jeg vil nu demonstrere et værktøj, som du kan gøre netop det med. Dette værktøj genererer automatisk en regelmæssig og robust

toString

metode til en bestemt klasse, hvilket næsten eliminerer tiden brugt på at udvikle metoden. Det centraliserer også

toString ()

format. Hvis du ændrer formatet, skal du gendanne

toString

metoder; dette er dog stadig meget lettere end manuelt at ændre hundreder eller tusinder af klasser.

Det er også let at vedligeholde den genererede kode. Hvis du tilføjer flere attributter i klasserne, kan du blive bedt om at foretage ændringer i toString metode også. Siden generationen af toString metoder er automatiseret, behøver du kun køre hjælpeprogrammet på klassen igen for at foretage dine ændringer. Dette er enklere og mindre udsat for fejl end den manuelle tilgang.

Koden

Denne artikel er ikke beregnet til at forklare Reflection API; følgende kode forudsætter, at du i det mindste har en forståelse af begreberne bag refleksion. Du kan besøge

Ressourcer

sektion for dokumentationen til Reflection API. Værktøjet er skrevet som følger:

pakke fareed.publications.utilities; import java.lang.reflect. *; public class ToStringGenerator {public static void main (String [] args) {if (args.length == 0) {System.out.println ("Angiv klassens navn som kommandolinjeargumentet"); System.exit (0); } prøv {Class targetClass = Class.forName (args [0]); if (! targetClass.isPrimitive () && targetClass! = String.class) {Feltfelter [] = targetClass.getDeclaredFields (); Klasse cSuper = targetClass.getSuperclass (); // Henter superklasse output ("StringBuffer buffer = ny StringBuffer (500);"); // Bufferkonstruktion, hvis (cSuper! = Null && cSuper! = Object.class) {output ("buffer.append (super.toString ());"); // Superklasses toString ()} for (int j = 0; j <fields.length; j ++) {output ("buffer.append (\" "+ fields [j] .getName () +" = \ "); "); // Tilføj feltnavn, hvis (felter [j] .getType (). IsPrimitive () || felter [j] .getType () == String.class) // Kontroller, om der er primitiv eller strengoutput ("buffer.append ( dette. "+ felter [j] .getName () +"); "); // Tilføj den primitive feltværdi ellers {/ * Det er IKKE et primitivt felt, så dette kræver en kontrol af NULL-værdien for det samlede objekt * / output ("hvis (dette." + Felter [j] .getName () + "! = null)"); output ("buffer.append (dette." + felter [j] .getName () + ".toString ());"); output ("ellers buffer.append (\" værdi er nul \ ");"); } // slutningen af ​​andet} // slutningen af ​​for loop-output ("return buffer.toString ();"); }} fange (ClassNotFoundException e) {System.out.println ("Klasse ikke fundet i klassestien"); System.exit (0); }} privat statisk ugyldig output (String data) {System.out.println (data); }} 

Kodeudgangskanalen

Kodens format afhænger også af kravene til dit projektværktøj. Nogle udviklere foretrækker måske at have koden i en brugerdefineret fil på disken. Andre udviklere er tilfredse med

system.out

konsol, som giver dem mulighed for at kopiere og integrere koden i den faktiske fil manuelt. Jeg overlader simpelthen disse muligheder til dig og bruger den enkleste metode:

system.out

udsagn.

Begrænsninger i tilgangen

Der er to vigtige begrænsninger for denne tilgang. Den første er, at den ikke understøtter objekter, der indeholder cyklusser. Hvis objekt A indeholder en henvisning til objekt B, som derefter indeholder en henvisning til objekt A, fungerer dette værktøj ikke. Imidlertid vil denne sag være sjælden for mange projekter.

Den anden begrænsning er, at tilføjelse eller fratrækning af medlemsvariabler kræver regenerering af toString metode. Da dette skal gøres med eller uden værktøjet, er det ikke et problem specifikt for denne tilgang.

Konklusion

I denne artikel har jeg forklaret et lille automatiseringsværktøj, der virkelig kan forbedre udviklerens produktivitet og spille en lille, men vigtig rolle i reduktionen af ​​de samlede projekttidslinjer.


Opfølgningstips

Efter at dette tip blev offentliggjort, modtog jeg et par forslag fra læsere om, hvordan jeg kunne forbedre koden. I denne opfølgning forklarer jeg, hvordan jeg har opdateret værktøjet baseret på disse forslag og min egen indsigt. Du kan finde kildekoden til disse forbedringer i Ressourcer.

Forbedring nr. 1, foreslået af Sangeeta Varma

I min oprindelige kode håndterede jeg ikke arraytyperne for objektet og den primitive datatype; den nye kode håndterer nu matrixdata. Koden går dog kun op til enkeltdimensionelle arrays og fungerer ikke for multiple dimension arrays. Jeg har ikke været i stand til at komme med en generisk løsning på dette problem, da der efter min bedste viden ikke er nogen begrænsning for antallet af dimensioner for datatyper i Java (den eneste begrænsning er den tilgængelige hukommelse). Jeg glæder mig over enhver feedback, du kan tilbyde til en løsning.

Forbedring nr. 2, foreslået af Chris Sanscraint

Oprindeligt foreslog jeg hjælpeprogrammet til udviklingstid og ikke til runtime-miljøet. Det kan være meget praktisk at lade værktøjet køre i løbetid, men det kan tage et par flere CPU-cyklusser. Dog er objektet dumping / debugging (grundlæggende brug af toString ()) udføres normalt i løbet af udviklingstiden og slukkes for produktionsmiljøet. I nogle tilfælde er denne frakobling muligvis ikke mulig i produktionsmiljøet, da nogle projekter muligvis bruger toString () til forretningslogiske formål. Jeg foreslår at tage denne beslutning projekt-for-projekt-basis.

Før jeg udviklede dette værktøj, havde jeg allerede denne runtime-fleksibilitet i mit sind. Først udviklede jeg en separat delegeringsklasse, der blev brugt af enhver klientklasse til at generere toString (). Klassen genererede det ved hjælp af en metodeopkald som returner ToStringGenerator.generateToString (dette), hvor det her peger på den aktuelle forekomst af klientklassen, og kodeerklæringen er skrevet i toString () metodeimplementering. Men denne tilgang mislykkedes, fordi Reflection API ikke har evnen til at hente værdierne for de private medlemmer ved kørsel. Så klassen var kun nyttig for offentlige medlemmer, hvilket jeg ikke ønskede.

Men så påpegede Sanscraint, at den samme Reflection API-kode får værdien af ​​de private medlemmer ved kørsel, når koden skrives inden for en metode af samme opkaldsklasse. Så jeg har opdateret værktøjet, der skal bruges ved kørsel, og derudover toString () metode behøver aldrig at blive opdateret eller redigeret til subtraktion eller tilføjelse af nogen attributter i målklassen.

Forbedring nr. 3, foreslået af Eric Ye

Oprindeligt brugte jeg det her præfiks for medlemsvariablerne adgang i den genererede kode, men Mr. Ye påpegede, at koden også kan bruges i en statisk metode eller endda til at udsende statiske medlemmer. Så den opdaterede kode kan nu håndtere både klasse- og instansmedlemmer. Mr. Ye identificerede også en fejl, som er rettet i denne version, der fik klassen til at generere ubrugelig kode til attributløse klasser.

Kodemodifikationer

Efter at have gjort værktøjets runtime-aktiveret var jeg frustreret over at skulle kopiere / indsætte metoderne i hver klasse, hvilket blev vanskeligt, da den nye kode bestod af flere metoder.

En løsning ville være at oprette en grænseflade / abstrakt baseklasse, der i det mindste ville løse problemet med metodesignaturer, men kopi / indsæt er stadig påkrævet. Den abstrakte løsning i basisklassen vil også begrænse klienten fra at komme fra en anden klasse.

En indre klasse har dog evnen til at få adgang til de private medlemmer af moderklassen, så reflektionskoden, der kører inden for dens metoder, også kunne få de private værdier. Så jeg besluttede at ændre værktøjet til en indre klasse, der kunne indsættes i enhver overordnet klientklasse. Jeg har også leveret ToStringGeneratorExample.java, der bruger ToStringGenerator.java som den indre klasse til at implementere toString () metode.

Endelig vil jeg takke de mennesker, der fremsatte deres forslag til forbedring af denne tilgang.

Syed Fareed Ahmad er en Java-programmør, designer og arkitekt i Lahore, Pakistan. Han er involveret i udviklingen af ​​Java- (Servlets, JSP og EJB), WebSphere- og XML-baserede e-business-løsninger.

Lær mere om dette emne

  • For opfølgningskildekoden

    //images.techhive.com/downloads/idge/imported/article/jvw/2000/08/jw-javatip99.zip

  • Refleksionsdokumentation på Suns websted

    //java.sun.com/products/jdk/1.1/docs/guide/reflection/index.html

  • Se alle forrige Java-tip og indsend din egen

    //www.javaworld.com/javatips/jw-javatips.index.html

Denne historie, "Java Tip 99: Automate toString () creation" blev oprindeligt udgivet af JavaWorld.