Programmering

Effektiv håndtering av Java NullPointerException

Det tar ikke mye Java-utviklingserfaring å lære på førstehånd hva NullPointerException handler om. Faktisk har en person fremhevet å håndtere dette som den største feilen Java-utviklere gjør. Jeg blogget tidligere om bruk av String.value (Object) for å redusere uønskede NullPointerExceptions. Det er flere andre enkle teknikker man kan bruke for å redusere eller eliminere forekomster av denne vanlige typen RuntimeException som har fulgt med oss ​​siden JDK 1.0. Dette blogginnlegget samler og oppsummerer noen av de mest populære av disse teknikkene.

Sjekk hvert objekt for null før bruk

Den sikreste måten å unngå NullPointerException er å sjekke alle objektreferanser for å sikre at de ikke er null før du får tilgang til et av objektets felt eller metoder. Som følgende eksempel indikerer, er dette en veldig enkel teknikk.

final String causeStr = "legge til streng i Deque som er satt til null."; final String elementStr = "Fudd"; Deque deque = null; prøv {deque.push (elementStr); logg ("Vellykket på" + causeStr, System.out); } fange (NullPointerException nullPointer) {logg (causeStr, nullPointer, System.out); } prøv {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); logg ("Vellykket ved" + causeStr + "(ved først å sjekke om null og øyeblikkelig å implementere Deque-implementering)", System.out); } fange (NullPointerException nullPointer) {logg (causeStr, nullPointer, System.out); } 

I koden ovenfor initialiseres Deque brukt med vilje til null for å forenkle eksemplet. Koden i den første prøve blokken sjekker ikke for null før du prøver å få tilgang til en Deque-metode. Koden i den andre prøve blokken kontrollerer om det er null og instantierer en implementering av Deque (LinkedList) hvis den er null. Resultatet fra begge eksemplene ser slik ut:

FEIL: NullPointerException oppstod mens du prøvde å legge til streng til Deque som er satt til null. java.lang.NullPointerException INFO: Vellykket med å legge streng til Deque som er satt til null. (ved først å sjekke om null og øyeblikkelig implementering av Deque) 

Meldingen etter FEIL i utgangen ovenfor indikerer at a NullPointerException kastes når en metodeanrop blir forsøkt på null Deque. Meldingen etter INFO i utgangen ovenfor indikerer at ved å sjekke Deque for null først og deretter sette i gang en ny implementering for den når den er null, ble unntaket helt unngått.

Denne tilnærmingen brukes ofte, og som vist ovenfor, kan den være veldig nyttig for å unngå uønsket (uventet) NullPointerException tilfeller. Det er imidlertid ikke uten kostnader. Å sjekke om null før hvert objekt brukes, kan oppblåse koden, kan være kjedelig å skrive, og åpner mer rom for problemer med utvikling og vedlikehold av tilleggskoden. Av denne grunn har det vært snakk om å innføre Java-språkstøtte for innebygd nulldeteksjon, automatisk legge til disse sjekkene for null etter den første kodingen, null-sikre typer, bruk av Aspect-Oriented Programming (AOP) for å legge til nullkontroll for å byte kode og andre null-deteksjonsverktøy.

Groovy gir allerede en praktisk mekanisme for å håndtere objektreferanser som potensielt er null. Groovys trygge navigasjonsoperatør (?.) returnerer null i stedet for å kaste a NullPointerException når en null objektreferanse er tilgjengelig.

Siden det å sjekke null for hver objektreferanse kan være kjedelig og ikke oppblåser koden, velger mange utviklere å velge med hensiktsmessighet hvilke objekter de skal sjekke for null. Dette fører vanligvis til sjekking av null på alle objekter av potensielt ukjent opprinnelse. Tanken her er at gjenstander kan kontrolleres ved eksponerte grensesnitt og deretter antas å være trygge etter den første kontrollen.

Dette er en situasjon der den ternære operatøren kan være spesielt nyttig. I stedet for

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

den ternære operatøren støtter denne mer konsise syntaksen

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

Kontroller metoden Argumenter for Null

Den nettopp diskuterte teknikken kan brukes på alle objekter. Som nevnt i beskrivelsen av denne teknikken, velger mange utviklere å bare sjekke objekter for null når de kommer fra "ikke-klarerte" kilder. Dette betyr ofte å teste for null først i metoder som er utsatt for eksterne innringere. For eksempel, i en bestemt klasse, kan utvikleren velge å se etter null på alle objekter som sendes til offentlig metoder, men ikke sjekk om det er null privat metoder.

Følgende kode demonstrerer denne kontrollen for null ved metodeoppføring. Den inkluderer en enkelt metode som den demonstrative metoden som snur og kaller to metoder, og sender hver metode et enkelt nullargument. En av metodene som mottar et nullargument, sjekker argumentet først for null, men den andre antar bare at den innleverte parameteren ikke er null.

 / ** * Legg til forhåndsdefinert tekststreng til den medfølgende StringBuilder. * * @param builder StringBuilder som vil ha tekst vedlagt; skal * være ikke-null. * @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 null; ikke-null verdi må oppgis."); } builder.append ("Takk for at du leverer en StringBuilder."); } / ** * Legg til forhåndsdefinert tekststreng til den medfølgende StringBuilder. * * @param builder StringBuilder som vil ha tekst vedlagt; skal * være ikke-null. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (final StringBuilder builder) {builder.append ("Thanks for supplying a StringBuilder."); } / ** * Demonstrer effekten av å sjekke parametere for null før du prøver å bruke * innlagte parametere som potensielt er null. * / public void demonstrCheckingArgumentsForNull () {final String causeStr = "gi metoden null som argument."; logHeader ("DEMONSTRASJON AV KONTROLLMETODEPARAMETRE FOR NULL", System.out); prøv {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } fange (NullPointerException nullPointer) {logg (causeStr, nullPointer, System.out); } prøv {appendPredefinedTextToProvidedBuilderCheckForNull (null); } fange (IllegalArgumentException illegalArgument) {log (causeStr, illegalArgument, System.out); }} 

Når koden ovenfor kjøres, vises utdataene som vist neste.

FEIL: NullPointerException oppstod mens du prøvde å gi null til metoden som argument. java.lang.NullPointerException FEIL: IllegalArgumentException oppstått mens du prøvde å gi null til metode som argument. java.lang.IllegalArgumentException: Den medfølgende StringBuilder var null; ikke-null verdi må oppgis. 

I begge tilfeller ble en feilmelding logget. Imidlertid kastet det tilfelle der en null ble sjekket for, et annonsert IllegalArgumentException som inkluderte tilleggsinformasjon om når null ble oppdaget. Alternativt kan denne nullparameteren ha blitt håndtert på en rekke måter. For tilfellet der en nullparameter ikke ble håndtert, var det ingen muligheter for hvordan man skulle håndtere den. Mange foretrekker å kaste a NullPolinterException med ytterligere kontekstinformasjon når en null eksplisitt oppdages (se artikkel nr. 60 i andre utgave av Effektiv Java eller vare nr. 42 i første utgave), men jeg har en liten preferanse for IllegalArgumentException når det eksplisitt er et metodeargument som er null fordi jeg tror selve unntaket legger til kontekstdetaljer og det er lett å inkludere "null" i emnet.

Teknikken for å sjekke metodeargumenter for null er egentlig en delmengde av den mer generelle teknikken for å sjekke alle objekter for null. Imidlertid, som beskrevet ovenfor, er argumenter for offentlig eksponerte metoder ofte minst pålitelige i en applikasjon, og det kan derfor være viktigere å sjekke dem enn å sjekke gjennomsnittsobjektet for null.

Kontroll av metodeparametere for null er også en delmengde av den mer generelle praksisen med å sjekke metodeparametere for generell gyldighet, som diskutert i nr. 38 i andre utgave av Effektiv Java (Vare 23 i første utgave).

Tenk på primitiver snarere enn gjenstander

Jeg tror ikke det er noen god ide å velge en primitiv datatype (for eksempel int) over den tilsvarende objektreferansetypen (for eksempel Integer) bare for å unngå muligheten for a NullPointerException, men det kan ikke benektes at en av fordelene med primitive typer er at de ikke fører til NullPointerExceptions. Imidlertid må primitive fortsatt kontrolleres for gyldighet (en måned kan ikke være et negativt heltall), og derfor kan denne fordelen være liten. På den annen side kan primitive ikke brukes i Java-samlinger, og det er tider man vil ha muligheten til å sette en verdi til null.

Det viktigste er å være veldig forsiktig med kombinasjonen av primitiver, referansetyper og autoboksing. Det er en advarsel i Effektiv Java (Andre utgave, vare nr. 49) angående farene, inkludert kasting av NullPointerException, relatert til uforsiktig blanding av primitive og referansetyper.

Tenk nøye over lenker med lenket metode

EN NullPointerException kan være veldig lett å finne fordi et linjenummer vil si hvor det skjedde. For eksempel kan et stabelspor se ut som det som vises neste:

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

Stablesporet gjør det åpenbart at NullPointerException ble kastet som et resultat av kode utført på linje 222 av UnngåNullPointerExamples.java. Selv med linjenummeret som er oppgitt, kan det fortsatt være vanskelig å begrense hvilket objekt som er null hvis det er flere objekter med metoder eller felt tilgjengelig på samme linje.

For eksempel en uttalelse som someObject.getObjectA (). getObjectB (). getObjectC (). toString (); har fire mulige samtaler som kan ha kastet NullPointerException tilskrevet den samme kodelinjen. Å bruke en feilsøkingsprogram kan hjelpe med dette, men det kan være situasjoner når det er å foretrekke å bare bryte opp ovennevnte kode slik at hver samtale utføres på en egen linje. Dette gjør at linjenummeret i en stabelsporing enkelt kan indikere hvilken eksakt samtale som var problemet. Videre forenkler det eksplisitt å sjekke hvert objekt for null. Imidlertid øker koden ved å bryte opp koden linjene for koden (for noen som er positive!), Og det kan ikke alltid være ønskelig, spesielt hvis man er sikker på at ingen av metodene i spørsmålet noen gang vil være null.

Gjør NullPointerExceptions mer informativ

I ovennevnte anbefaling var advarselen å vurdere nøye bruk av metoden samtalekjetting, først og fremst fordi det gjorde at linjenummeret i stabelsporet for en NullPointerException mindre nyttig enn det ellers kan være. Linjenummeret vises imidlertid bare i en stakksporing når koden ble kompilert med feilsøkingsflagget slått på. Hvis den ble kompilert uten feilsøking, ser stakksporingen slik ut som vist neste:

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

Som ovennevnte viser, er det et metodenavn, men ikke noe linjenummer for NullPointerException. Dette gjør det vanskeligere å umiddelbart identifisere hva som i koden førte til unntaket. En måte å løse dette på er å gi kontekstinformasjon i alle kast NullPointerException. Denne ideen ble demonstrert tidligere da en NullPointerException ble fanget og kastet på nytt med ytterligere kontekstinformasjon som en IllegalArgumentException. Imidlertid, selv om unntaket bare blir kastet på nytt som et annet NullPointerException med kontekstinformasjon, er det fortsatt nyttig. Kontekstinformasjonen hjelper personen med å feilsøke koden for raskere å identifisere den virkelige årsaken til problemet.

Følgende eksempel viser dette prinsippet.

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) {throw new NullPointerException ("Kunne ikke trekke ut datoen fra den angitte kalenderen"); } sluttdato dato = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException med nyttige data", nullPointer, System.out); } 

Resultatet fra å kjøre ovennevnte kode ser slik ut.

FEIL: NullPointerException oppstått mens du prøvde å NullPointerException med nyttige data java.lang.NullPointerException FEIL: NullPointerException oppstått mens du prøvde å NullPointerException med nyttige data java.lang.NullPointerException: Kunne ikke trekke ut datoen fra den angitte kalenderen 
$config[zx-auto] not found$config[zx-overlay] not found