Programmering

For mange parametre i Java-metoder, del 6: Metoden vender tilbage

I den aktuelle serie af indlæg, som jeg skriver om at reducere antallet af parametre, der kræves for at kalde Java-metoder og konstruktører, har jeg hidtil fokuseret på tilgange, der direkte påvirker parametrene selv (brugerdefinerede typer, parametreobjekter, bygmønster, metodeoverbelastning og metode navngivning). I betragtning af dette kan det virke overraskende for mig at afsætte et indlæg i denne serie til, hvordan Java-metoder giver returværdier. Metodernes returværdier kan dog påvirke de parametre, metoderne accepterer, når udviklere vælger at give "retur" -værdier ved at indstille eller ændre de angivne parametre snarere end eller i tillæg til mere traditionelle metode-returmekanismer.

De "traditionelle måder", som en ikke-konstruktormetode returnerer en værdi på, kan begge specificeres i metodesignaturen. Den mest almindeligt anerkendte tilgang til returnering af en værdi fra en Java-metode er via dens erklærede returtype. Dette fungerer ofte godt, men en af ​​frustrationer, der oftest opstår, er at få lov til kun at returnere en værdi fra en Java-metode ..

Java's undtagelseshåndteringsmekanisme er også en anden tilgang til at bevare et "resultat" af en metode til opkaldere. Især kontrollerede undtagelser annonceres for den, der ringer op via kast-klausulen. Faktisk siger Jim Waldo i sin bog Java: The Good Parts, at det er lettere at forstå Java-undtagelser, når man tænker på Java-undtagelser som en anden type metode, der er begrænset til at være en kastbar type.

Selvom metodens returtype og kastede undtagelser er beregnet som de primære tilgange til metoder til at returnere information til opkaldere, er det undertiden fristende at returnere data eller tilstand via de parametre, der sendes til metoden. Når en metode skal returnere mere end et stykke information, kan Java-metodernes returnering af enkeltværdier virke begrænsende. Selvom undtagelser giver en anden måde at kommunikere tilbage til den, der ringer op, ser det næsten ud til at være enigt om, at undtagelser kun skal bruges til rapportering af ekstraordinære situationer og ikke til rapportering af "normale" data eller bruges i kontrolflow. I betragtning af at kun et objekt eller primitivt kan returneres fra en metode, og at undtagelser kun tillader returnering af en Kan kastes og bør kun bruges til at rapportere ekstraordinære situationer, bliver det stadig mere attraktivt for Java-udvikleren at kapre parametre som en alternativ rute til at returnere data til den, der ringer op.

Den teknik, som en udvikler kan bruge til at anvende metodeparametre som bærere for returneringsdata, er at acceptere parametre, der er mutable, og at mutere de indleverede objekters tilstand. Disse ændrede objekter kan få deres indhold ændret efter metoden, og derefter kan den, der ringer op, få adgang til det objekt, det leverede for at bestemme dets nye tilstandsindstillinger, der er blevet anvendt ved den kaldte metode. Selvom dette kan gøres med ethvert mutabelt objekt, synes samlinger at være særligt attraktive for udvikleren, der forsøger at videregive værdier til opkalderen via parametre.

Der er nogle ulemper ved at sende tilstanden tilbage til den kaldte via de angivne parametre. Denne tilgang overtræder ofte princippet om mindst forbløffelse, da de fleste Java-udviklere sandsynligvis forventer, at parametre er indgående snarere end UDGÅENDE (og Java giver ikke nogen kodestøtte til at specificere forskellen). Bob Martin udtrykker det sådan i sin bog Clean Code, "Generelt skal outputargumenter undgås." En anden ulempe ved at bruge argumenter som et middel til en metode til at give tilstand eller output til den, der ringer, er, at dette føjer til rodet af argumenter, der sendes til en metode. Med dette i tankerne fokuserer resten af ​​dette indlæg på alternativer til at returnere flere værdier via indleverede parametre.

Selvom Java-metoder kun kan returnere et enkelt objekt eller primitivt, er dette virkelig ikke meget af en begrænsning, når man overvejer, at et objekt kan være næsten alt, hvad vi vil have det. Der er flere tilgange, som jeg har set, men ikke kan anbefale. En af disse returnerer en matrix eller samling af objektforekomster med hver Objekt at være en forskellig og tydelig og ofte ikke-relateret "ting". For eksempel kan metoden returnere tre værdier som tre elementer i en matrix eller samling. En variation af denne tilgang er at bruge en par tuple eller n-størrelse tuple til at returnere flere tilknyttede værdier. En anden variation på denne tilgang er at returnere et Java Map, der kortlægger vilkårlige nøgler til deres tilknyttede værdi. Som med de andre løsninger lægger denne tilgang unødig byrde på klienten at vide, hvad disse nøgler er, og få adgang til kortværdierne gennem disse nøgler.

Den næste kodeliste indeholder flere af disse mindre attraktive tilgange til at returnere flere værdier uden at kapre metodeparametrene for at returnere flere værdier.

Returnering af flere værdier via generiske datastrukturer

 // ==================================================== =============== // BEMÆRK: Disse eksempler er udelukkende beregnet til at illustrere et punkt // og anbefales IKKE til produktionskode. // =================================================== ================ / ** * Angiv filmoplysninger. * * @retur Filmoplysninger i form af et array, hvor detaljer kortlægges til * elementer med følgende indekser i arrayet: * 0: Filmtitel * 1: Udgivet år * 2: Director * 3: Rating * / public Object [] getMovieInformation () {final Object [] movieDetails = {"World War Z", 2013, "Marc Forster", "PG-13"}; returnere filmDetaljer; } / ** * Angiv filmoplysninger. * * @return Filmoplysninger i form af en liste, hvor der gives detaljer * i denne rækkefølge: Filmtitel, år udgivet, instruktør, vurdering. * / public List getMovieDetails () {return Arrays.asList ("Ender's Game", 2013, "Gavin Hood", "PG-13"); } / ** * Angiv filmoplysninger. * * @return Filmoplysninger i kortform. Filmens karakteristika kan * erhverves ved at kigge på kortet efter disse nøgleelementer: "Titel", "År", * "Direktør" og "Rating" ./ * / public Map getMovieDetailsMap () {final HashMap map = new HashMap (); map.put ("Titel", "Foragtelig mig 2"); map.put ("År", 2013); map.put ("Director", "Pierre Coffin and Chris Renaud"); map.put ("Rating", "PG"); returkort; } 

De ovennævnte tilgange opfylder hensigten om ikke at videregive data tilbage til den, der ringer op via de påkaldte metodeparametre, men der er stadig unødvendig byrde på den, der ringer op, for at kende intime detaljer om den returnerede datastruktur. Det er rart at reducere antallet af parametre til metoden og ikke overtræde princippet om mindst overraskelse, men det er ikke så rart at kræve, at klienten kender indviklingen i en kompleks datastruktur.

Jeg foretrækker at skrive tilpassede objekter til mine afkast, når jeg har brug for at returnere mere end en værdi. Det er lidt mere arbejde end at bruge en matrix, samling eller tuple-struktur, men den meget lille mængde ekstra arbejde (typisk et par minutter med moderne Java IDE'er) betaler sig med læsbarhed og flydende funktion, der ikke er tilgængelig med disse mere generiske tilgange. I stedet for at skulle forklare med Javadoc eller kræve, at brugere af min kode læser min kode omhyggeligt for at vide, hvilke parametre der er angivet i hvilken rækkefølge i arrayet eller samlingen, eller hvilken værdi der er i tuplen, kan mine tilpassede returobjekter have metoder defineret på dem, der fortæller klienten nøjagtigt, hvad de leverer.

Kodestykkerne, der følger, illustrerer et simpelt Film klasse genereret i vid udstrækning af NetBeans, der kan bruges som returtype sammen med koden, der kunne returnere en forekomst af den klasse snarere end en mere generisk og mindre læselig datastruktur.

Film.java

pakke dustin. eksempler; importere java.util.Objects; / ** * Enkel filmklasse for at demonstrere, hvor let det er at give flere værdier * i en enkelt Java-metode returnere og give læsbarhed til klienten. * * @forfatter Dustin * / public class Film {privat final String movieTitle; privat afsluttende int år frigivet; privat final String movieDirectorName; privat final String movieRating; offentlig film (String movieTitle, int yearReleased, String movieDirectorName, String movieRating) {this.movieTitle = movieTitle; this.yearReleased = yearReleased; this.movieDirectorName = movieDirectorName; this.movieRating = movieRating; } offentlig String getMovieTitle () {returner movieTitle; } public int getYearReleased () {return yearReleased; } offentlig streng getMovieDirectorName () {returner movieDirectorName; } offentlig String getMovieRating () {returner movieRating; } @ Override public int hashCode () {int hash = 3; hash = 89 * hash + Objects.hashCode (denne filmtitel); hash = 89 * hash + this.yearReleased; hash = 89 * hash + Objects.hashCode (this.movieDirectorName); hash = 89 * hash + Objects.hashCode (denne filmrating); retur hash; } @Override offentlige boolske lig (Objekt obj) {hvis (obj == null) {returner false; } hvis (getClass ()! = obj.getClass ()) {return false; } final Movie other = (Film) obj; hvis (! Objects.equals (this.movieTitle, other.movieTitle)) {return false; } hvis (this.yearReleased! = other.yearReleased) {returner falsk; } hvis (! Objects.equals (this.movieDirectorName, other.movieDirectorName)) {return false; } hvis (! Objects.equals (this.movieRating, other.movieRating)) {return false; } returner sandt } @Override public String toString () {return "Movie {" + "movieTitle =" + movieTitle + ", yearReleased =" + yearReleased + ", movieDirectorName =" + movieDirectorName + ", movieRating =" + movieRating + '}'; }} 

Returnering af flere detaljer i et enkelt objekt

 / ** * Angiv filmoplysninger. * * @return Filmoplysninger. * / public Movie getMovieInfo () {returner ny film ("Oblivion", 2013, "Joseph Kosinski", "PG-13"); } 

Den enkle skrivning af Film klasse tog mig cirka 5 minutter. Jeg brugte NetBeans-klasseskabningsguiden til at vælge klassens navn og pakke, og derefter skrev jeg de fire attributter i klassen. Derfra brugte jeg simpelthen NetBeans "Insert Code" -mekanisme til at indsætte "get" accessor-metoder sammen med tilsidesatte toString (), hashCode () og lig (Object) -metoder. Hvis jeg ikke troede, jeg havde brug for noget af det, kunne jeg holde klassen enklere, men det er virkelig let at oprette, som det er. Nu har jeg en meget mere anvendelig returtype, og dette afspejles i den kode, der bruger klassen. Det har ikke brug for næsten lige så meget Javadoc-kommentarer om returtypen, fordi den typen taler for sig selv og annoncerer for dens indhold med sine "get" -metoder. Jeg føler, at den lille ekstra indsats for at oprette disse enkle klasser til returnering af flere værdier betaler sig med enorme udbytter sammenlignet med alternativer såsom tilbagevendende tilstand via metodeparametre eller brug af mere generiske og sværere at bruge returdatastrukturer.

Det er ikke så overraskende, at en brugerdefineret type til at indeholde de flere værdier, der skal returneres til en opkalder, er en attraktiv løsning. Når alt kommer til alt er dette begrebsmæssigt meget lig de begreber, jeg blogget om tidligere, relateret til brug af brugerdefinerede typer og parametreobjekter til at videregive flere relaterede parametre i stedet for at sende dem alle individuelt. Java er et objektorienteret sprog, og det overrasker mig, når jeg ikke ser objekter, der bruges oftere i Java-kode til at organisere parametre OG returnere værdier i en pæn pakke.

Fordele og fordele

Fordelene ved at bruge brugerdefinerede parameterobjekter til at repræsentere og indkapsle flere returværdier er indlysende. Parametre til metoden kan forblive "input" -parametre, fordi al outputinformation (undtagen fejlinformation, der kommunikeres via undtagelsesmekanismen) kan tilvejebringes i det brugerdefinerede objekt, der returneres ved metoden. Dette er en renere tilgang end at bruge generiske arrays, samlinger, kort, tupler eller andre generiske datastrukturer, fordi alle disse alternative tilgange skifter udviklingsindsats til alle potentielle kunder.

Omkostninger og ulemper

Jeg ser meget lidt ulempe ved at skrive tilpassede typer med flere værdier, der skal bruges som returtyper fra Java-metoder. Måske er den hyppigst krævede pris prisen på at skrive og teste disse klasser, men omkostningerne er ret små, fordi disse klasser har tendens til at være enkle, og fordi moderne IDE'er gør det meste af arbejdet for os. Fordi IDE'erne gør det automatisk, er koden typisk korrekt. Klasser er så enkle, at de let kan læses af kodevurderere, og de er nemme at teste.