Programmering

Effektiv Java NullPointerException-håndtering

Det tager ikke meget Java-udviklingserfaring at lære på førstehånd, hvad NullPointerException handler om. Faktisk har en person fremhævet at håndtere dette som den største fejl Java-udviklere laver. Jeg bloggede tidligere om brug af String.value (Object) for at reducere uønskede NullPointerExceptions. Der er flere andre enkle teknikker, man kan bruge til at reducere eller eliminere forekomsterne af denne almindelige type RuntimeException, der har været med os siden JDK 1.0. Dette blogindlæg samler og opsummerer nogle af de mest populære af disse teknikker.

Kontroller, at hvert objekt er nul, inden det bruges

Den mest sikre måde at undgå en NullPointerException er at kontrollere alle objektreferencer for at sikre, at de ikke er nul, før du får adgang til et af objektets felter eller metoder. Som det følgende eksempel indikerer, er dette en meget enkel teknik.

final String causeStr = "tilføjelse af streng til Deque, der er indstillet til null."; final String elementStr = "Fudd"; Deque deque = null; prøv {deque.push (elementStr); log ("Vellykket ved" + årsagStr, System.out); } fange (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } prøv {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Succesfuldt ved" + årsagStr + "(ved først at kontrollere for nullering og instantiering af Deque-implementering)", System.out); } fange (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } 

I koden ovenfor initialiseres den anvendte Deque med vilje til null for at lette eksemplet. Koden i den første prøve blok kontrollerer ikke for null, før den forsøger at få adgang til en Deque-metode. Koden i det andet prøve blok kontrollerer for null og instantierer en implementering af Deque (LinkedList), hvis den er nul. Outputtet fra begge eksempler ser sådan ud:

FEJL: NullPointerException stødte på under forsøg på at tilføje streng til Deque, der er indstillet til null. java.lang.NullPointerException INFO: Succesfuldt at tilføje streng til Deque, der er indstillet til null. (ved først at kontrollere for null og øjeblikkelig Deque-implementering) 

Meddelelsen efter FEJL i output ovenfor angiver, at a NullPointerException kastes, når et metodeopkald forsøges på nul Deque. Meddelelsen efter INFO i output ovenfor angiver det ved at kontrollere Deque for null først og derefter instantere en ny implementering for det, når det er null, undgik man undtagelsen helt.

Denne tilgang bruges ofte, og som vist ovenfor kan den være meget nyttig til at undgå uønskede (uventede) NullPointerException tilfælde. Det er dog ikke uden omkostninger. Hvis der kontrolleres for null, inden hvert objekt bruges, kan koden opblussen, kan være kedelig at skrive og åbner mere plads til problemer med udvikling og vedligeholdelse af den ekstra kode. Af denne grund har der været tale om at indføre Java-sprogstøtte til indbygget nulregistrering, automatisk tilføjelse af disse kontroller for nul efter den indledende kodning, nul-sikre typer, brug af Aspect-Oriented Programming (AOP) for at tilføje nulcheck til bytekode og andre nul-detektionsværktøjer.

Groovy giver allerede en bekvem mekanisme til at håndtere objektreferencer, der potentielt er nul. Groovys sikre navigationsoperatør (?.) returnerer null snarere end at kaste a NullPointerException når der er adgang til en null-objektreference.

Fordi kontrol af null for hver objektreference kan være kedelig og ikke spreder koden, vælger mange udviklere med omhu at vælge, hvilke objekter der skal kontrolleres for null. Dette fører typisk til kontrol af nul på alle objekter af potentielt ukendt oprindelse. Ideen her er, at objekter kan kontrolleres ved eksponerede grænseflader og derefter antages at være sikre efter den første kontrol.

Dette er en situation, hvor den ternære operatør kan være særlig nyttig. I stedet for

// hentet en BigDecimal kaldet someObject String returnString; hvis (someObject! = null) {returnString = someObject.toEngineeringString (); } andet {returnString = ""; } 

den ternære operatør understøtter denne mere kortfattede syntaks

// hentet en BigDecimal kaldet someObject final String returnString = (someObject! = null)? someObject.toEngineeringString (): ""; } 

Kontroller metode argumenter for nul

Den netop diskuterede teknik kan bruges på alle objekter. Som nævnt i denne tekniks beskrivelse, vælger mange udviklere kun at kontrollere objekter for nul, når de kommer fra "ikke-tillid til" kilder. Dette betyder ofte test for nul første ting i metoder, der udsættes for eksterne opkald. I en bestemt klasse kan f.eks. Udvikleren vælge at kontrollere nul på alle objekter, der sendes til offentlig metoder, men ikke kontrollere, om der er null ind privat metoder.

Den følgende kode demonstrerer denne kontrol for null ved metodeindtastning. Det inkluderer en enkelt metode som den demonstrative metode, der vender rundt og kalder to metoder, der sender hver metode et enkelt nullargument. En af metoderne, der modtager et nullargument, kontrollerer argumentet først for null, men den anden antager bare, at den indsendte parameter ikke er null.

 / ** * Føj foruddefineret tekst String til den medfølgende StringBuilder. * * @param builder StringBuilder, der vil have tekst vedhæftet; skal * være ikke-nul. * @throws IllegalArgumentException Kastes, hvis den medfølgende StringBuilder er * null. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (final StringBuilder builder) {if (builder == null) {throw new IllegalArgumentException ("Den medfølgende StringBuilder var nul; ikke-nul værdi skal angives.") } builder.append ("Tak, fordi du leverede en StringBuilder."); } / ** * Føj foruddefineret tekststreng til den medfølgende StringBuilder. * * @param builder StringBuilder, der vil have tekst vedhæftet; skal * være ikke-nul. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (final StringBuilder builder) {builder.append ("Tak for levering af en StringBuilder."); } / ** * Demonstrer effekt af kontrol af parametre for null, inden du prøver at bruge * indleverede parametre, der potentielt er null. * / public void demonstrCheckingArgumentsForNull () {final String causeStr = "give nul til metode som argument."; logHeader ("DEMONSTRATION AF KONTROLLER METODEPARAMETRE FOR NULL", System.out); prøv {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } fange (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } prøv {appendPredefinedTextToProvidedBuilderCheckForNull (null); } fange (IllegalArgumentException illegalArgument) {log (causeStr, illegalArgument, System.out); }} 

Når ovenstående kode udføres, vises output som vist næste.

FEJL: NullPointerException stødte på, mens man forsøgte at give null til metode som argument. java.lang.NullPointerException FEJL: IllegalArgumentException stødte på, mens du forsøgte at angive nul til metode som argument. java.lang.IllegalArgumentException: Den medfølgende StringBuilder var nul; ikke-nul-værdi skal angives. 

I begge tilfælde blev en fejlmeddelelse logget. Imidlertid kastede det tilfælde, hvor en nul blev kontrolleret for, en annonceret IllegalArgumentException, der indeholdt yderligere kontekstoplysninger om, hvornår nul blev fundet. Alternativt kunne denne null-parameter have været håndteret på en række forskellige måder. I det tilfælde, hvor en null-parameter ikke blev håndteret, var der ingen muligheder for, hvordan man håndterede den. Mange mennesker foretrækker at smide en NullPolinterException med de yderligere kontekstoplysninger, når en nul eksplicit er opdaget (se vare nr. 60 i anden udgave af Effektiv Java eller vare nr. 42 i første udgave), men jeg har en lille præference for IllegalArgumentException når det eksplicit er et metodeargument, der er null, fordi jeg tror, ​​at selve undtagelsen tilføjer kontekstdetaljer, og det er let at inkludere "null" i emnet.

Teknikken til kontrol af metodeargumenter for null er virkelig en delmængde af den mere generelle teknik til kontrol af alle objekter for null. Som beskrevet ovenfor er argumenter over for offentligt eksponerede metoder ofte de mindst tillid til en applikation, og det kan derfor være vigtigere at kontrollere dem end at kontrollere det gennemsnitlige objekt for null.

Kontrol af metodeparametre for null er også en delmængde af den mere generelle praksis med at kontrollere metodeparametre for generel gyldighed som diskuteret i punkt 38 ​​i anden udgave af Effektiv Java (Punkt 23 i første udgave).

Overvej primitive ting snarere end objekter

Jeg synes ikke det er en god ide at vælge en primitiv datatype (f.eks int) over dets tilsvarende objektreferenstype (såsom heltal) blot for at undgå muligheden for a NullPointerException, men der kan ikke benægtes, at en af ​​fordelene ved primitive typer er, at de ikke fører til NullPointerExceptions. Primitiver skal dog stadig kontrolleres for gyldighed (en måned kan ikke være et negativt heltal), og denne fordel kan derfor være lille. På den anden side kan primitive ikke bruges i Java-samlinger, og der er tidspunkter, man ønsker muligheden for at indstille en værdi til null.

Det vigtigste er at være meget forsigtig med kombinationen af ​​primitiver, referencetyper og autoboxing. Der er en advarsel i Effektiv Java (Anden udgave, vare nr. 49) angående farerne, herunder kastning af NullPointerException, relateret til skødesløs blanding af primitive og referencetyper.

Overvej nøje opkald med lænket metode

EN NullPointerException kan være meget let at finde, fordi et linjenummer angiver, hvor det skete. For eksempel kan et stakspor se ud som det, der vises næste:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (AvoidingNullPointerExamples.java:222) at dustin.examples.AvoidingNullPointerExamples.main (AvoidingNullPointerExamples) 

Stackspor gør det indlysende, at NullPointerException blev kastet som et resultat af kode udført på linje 222 i UndgåNullPointerExamples.java. Selv med det angivne linjenummer kan det stadig være vanskeligt at indsnævre, hvilket objekt der er null, hvis der er flere objekter med metoder eller felter til rådighed på den samme linje.

For eksempel en erklæring som someObject.getObjectA (). getObjectB (). getObjectC (). toString (); har fire mulige opkald, der muligvis har kastet NullPointerException tilskrevet den samme kodelinje. Brug af en debugger kan hjælpe med dette, men der kan være situationer, hvor det er at foretrække at blot bryde ovenstående kode op, så hvert opkald udføres på en separat linje. Dette gør det muligt for linjenummeret i en staksporing let at angive, hvilket nøjagtigt opkald der var problemet. Desuden letter det eksplicit at kontrollere hvert objekt for null. Imidlertid på ulempen øger kodebrydningen linjen for kodetælling (for nogle er det positivt!) Og er måske ikke altid ønskeligt, især hvis man er sikker på, at ingen af ​​de pågældende metoder nogensinde vil være null.

Gør NullPointerExceptions mere informativ

I ovenstående anbefaling var advarslen om nøje at overveje at bruge metoden opkaldskædning primært fordi det gjorde at have linjenummeret i staksporet for en NullPointerException mindre nyttigt, end det ellers måtte være. Linjenummeret vises dog kun i en staksporing, når koden blev kompileret med debugflagget slået til. Hvis det blev kompileret uden fejlretning, ser stakksporingen ud som vist næste:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (Unknown Source) at dustin.examples.AvoidingNullPointerExamples.main (Ukendt kilde) 

Som ovenstående output viser, er der et metodenavn, men ikke noget linjenummer for NullPointerException. Dette gør det vanskeligere med det samme at identificere, hvad der i koden førte til undtagelsen. En måde at tackle dette på er at give kontekstinformation i ethvert kast NullPointerException. Denne idé blev demonstreret tidligere, da en NullPointerException blev fanget og genkastet med yderligere kontekstoplysninger som en IllegalArgumentException. Men selvom undtagelsen simpelthen genkastes som en anden NullPointerException med kontekstoplysninger er det stadig nyttigt. Kontekstoplysningerne hjælper personen med at fejle koden for hurtigere at identificere den sande årsag til problemet.

Følgende eksempel viser dette princip.

final Calendar nullCalendar = null; prøv {final Date date = nullCalendar.getTime (); } fange (NullPointerException nullPointer) {log ("NullPointerException med nyttige data", nullPointer, System.out); } prøv {if (nullCalendar == null) {kast ny NullPointerException ("Kunne ikke udtrække datoen fra den leverede kalender"); } sidste dato dato = nullCalendar.getTime (); } fange (NullPointerException nullPointer) {log ("NullPointerException med nyttige data", nullPointer, System.out); } 

Outputtet fra at køre ovenstående kode ser ud som følger.

FEJL: NullPointerException stødte på, mens man forsøgte at NullPointerException med nyttige data java.lang.NullPointerException FEJL: NullPointerException stødte på, mens man forsøgte at NullPointerException med nyttige data java.lang.NullPointerException: Kunne ikke udtrække datoen fra den leverede kalender 
$config[zx-auto] not found$config[zx-overlay] not found