Programiranje

Učinkovito upravljanje z NullPointerException Java

Ni treba veliko izkušenj z razvojem Jave, da se iz prve roke naučite, za kaj gre pri NullPointerException. Pravzaprav je ena oseba to obravnavanje označila kot napako številka ena, ki jo naredijo razvijalci Java. Prej sem blogiral o uporabi String.value (Object) za zmanjšanje neželenih NullPointerExceptions. Obstaja več drugih preprostih tehnik, ki jih lahko uporabimo za zmanjšanje ali odpravo pojavov te običajne vrste RuntimeException, ki nas spremlja že od JDK 1.0. Ta objava v spletnem dnevniku zbira in povzema nekatere izmed najbolj priljubljenih teh tehnik.

Pred uporabo preverite, ali je vsak objekt nulen

Najbolj zanesljiv način, da se izognete NullPointerException, je preveriti vse sklice na objekte, da se prepričate, da niso nič pred dostopom do enega od polj ali metod predmeta. Kot kaže naslednji primer, gre za zelo preprosto tehniko.

final String reasonStr = "dodajanje niza v Deque, ki je nastavljen na nič."; končni niz elementStr = "Fudd"; Deque deque = nič; poskusite {deque.push (elementStr); log ("Uspešno ob" + causeStr, System.out); } catch (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } poskusite {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Uspešno ob" + causeStr + "(tako, da najprej preverimo ničnost in sprožimo izvajanje Deque)", System.out); } catch (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } 

V zgornji kodi se uporabljeni Deque namerno inicializira na nulo, da olajša primer. Koda v prvem poskusite blok ne preveri nič, preden poskuša dostopati do metode Deque. Koda v drugem poskusite blok preveri, ali je null, in sproži izvedbo Deque (LinkedList), če je nič. Izhod iz obeh primerov je videti tako:

NAPAKA: pri poskusu dodajanja niza v Deque, ki je nastavljen na nulo, je prišlo do NullPointerException. java.lang.NullPointerException INFO: Uspešno dodajanje Stringa v Deque, ki je nastavljen na nič. (tako, da najprej preverite ničnost in sprožite izvajanje Deque) 

Sporočilo, ki sledi ERROR v zgornjem izhodu, pomeni, da a NullPointerException se vrže, ko je poskus ničelne klice metode Deque. Sporočilo, ki sledi INFO v zgornjem izhodu, to nakazuje s preverjanjem Deque za null najprej in nato instantacijo nove izvedbe zanjo, ko je nič, se je izjema popolnoma izognila.

Ta pristop se pogosto uporablja in je, kot je prikazano zgoraj, lahko zelo koristen pri preprečevanju neželenih (nepričakovanih) NullPointerException primerov. Vendar pa ni brez stroškov. Preverjanje nule pred uporabo vsakega predmeta lahko napihne kodo, je lahko dolgočasno pisati in odpira več prostora za težave z razvojem in vzdrževanjem dodatne kode. Iz tega razloga so govorili o uvedbi podpore za jezik Java za vgrajeno zaznavanje ničelnih vrednosti, samodejnem dodajanju teh preverjanj za ničelno vrednost po začetnem kodiranju, nično varnih vrstah, uporabi Aspektno usmerjenega programiranja (AOP) za dodajanje ničelnega preverjanja v bajtno kodo in druga orodja za odkrivanje ničelnih vrednosti.

Groovy že ponuja priročen mehanizem za obravnavo referenc na objekte, ki so potencialno nične. Groovyjev operater varne navigacije (?.) vrne null in ne vrže a NullPointerException ko se dostopa do sklica na ničelni objekt.

Ker je preverjanje ničelnosti za vsak sklic na objekt lahko dolgočasno in napihne kodo, se mnogi razvijalci odločijo, da preudarno izberejo, katere predmete naj preverijo kot nične. To običajno pripelje do preverjanja ničelnosti na vseh objektih, ki so potencialno neznanega izvora. Ideja je v tem, da je mogoče predmete preveriti na izpostavljenih vmesnikih in potem po začetnem preverjanju domnevati, da so varni.

V tem primeru je lahko ternarni operater še posebej koristen. Namesto

// pridobljen je BigDecimal, imenovan someObject String returnString; if (someObject! = null) {returnString = someObject.toEngineeringString (); } else {returnString = ""; } 

ternarni operator podpira to bolj strnjeno sintakso

// pridobljen BigDecimal, imenovan someObject final String returnString = (someObject! = null)? someObject.toEngineeringString (): ""; } 

Preverite argumente metode za Nič

Ravno obravnavano tehniko lahko uporabimo na vseh predmetih. Kot je navedeno v opisu te tehnike, se mnogi razvijalci odločijo, da bodo predmete preverjali, če so nujni, samo če prihajajo iz "nezaupljivih" virov. To pogosto pomeni preizkus nujnosti pri metodah, izpostavljenih zunanjim kličočim. Na primer, v določenem razredu se lahko razvijalec odloči, da preveri, ali je v vseh predmetih, ki so bili predani, nič javnosti metode, vendar ne preverite, ali je v zasebno metode.

Naslednja koda prikazuje to preverjanje ničelne vrednosti pri vnosu metode. Kot demonstracijska metoda vključuje eno metodo, ki se obrne in pokliče dve metodi, pri čemer vsaki metodi posreduje en nulen argument. Ena od metod, ki prejme null argument, najprej preveri, ali je argument null, druga pa samo domneva, da posredovani parameter ni nič.

 / ** * V prednastavljeni niz besedila dodajte priloženi StringBuilder. * * @param builder StringBuilder, ki mu bo dodano besedilo; naj * ne bo nič. * @throws IllegalArgumentException Vržen, če je podani StringBuilder * null. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (končni graditelj StringBuilder) {if (builder == null) {throw new IllegalArgumentException ("Navedeni StringBuilder je bil null; treba je določiti ne-null vrednost."); } builder.append ("Hvala, ker ste poslali StringBuilder."); } / ** * Priloženemu besedilnemu nizu dodajte priloženi StringBuilder. * * @param builder StringBuilder, ki mu bo dodano besedilo; ne sme biti nič. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (končni graditelj StringBuilder) {builder.append ("Hvala, ker ste poslali StringBuilder."); } / ** * Dokažite učinek preverjanja parametrov za nulo, preden poskusite uporabiti * posredovane parametre, ki so potencialno nični. * / javna void demonstrateCheckingArgumentsForNull () {final String vzrokStr = "kot argument poda null metodi."; logHeader ("PRIKAZOVANJE PARAMETROV PREVERJANJA METODE ZA NULL", System.out); poskusite {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } catch (NullPointerException nullPointer) {log (causeStr, nullPointer, System.out); } poskusite {appendPredefinedTextToProvidedBuilderCheckForNull (null); } catch (IllegalArgumentException nezakonitArgument) {log (causeStr, nezakonitArgument, System.out); }} 

Ko se zgornja koda izvede, se izhod prikaže, kot je prikazano naprej.

NAPAKA: NullPointerException je naletel med poskusom podajanja metode nule kot argument. java.lang.NullPointerException NAPAKA: IllegalArgumentException je naletel med poskusom metode kot argument določiti ničelno. java.lang.IllegalArgumentException: Navedeni StringBuilder je bil nič; treba je določiti ne-null vrednost. 

V obeh primerih je bilo zabeleženo sporočilo o napaki. Vendar je primer, v katerem je bila preverjena ničnost, vrgel oglaševani IllegalArgumentException, ki je vključeval dodatne informacije o kontekstu, kdaj je bila ugotovljena ničelna vrednost. S tem ničelnim parametrom bi lahko ravnali na različne načine. V primeru, ko ni bil obdelan ničelni parameter, ni bilo možnosti, kako ga obravnavati. Mnogi ljudje raje vržejo a NullPolinterException z dodatnimi informacijami o kontekstu, ko je nula izrecno odkrita (glej točko št. 60 v drugi izdaji Učinkovita Java ali Element # 42 v prvi izdaji), vendar imam nekoliko prednost IllegalArgumentException ko gre za izrecno argument metode, ki je ničen, ker mislim, da prav izjema doda podrobnosti o kontekstu in je enostavno vključiti "null" v zadevo.

Tehnika preverjanja argumentov metode za null je v resnici podskupina splošnejše tehnike preverjanja null za vse predmete. Kot je navedeno zgoraj, pa so argumenti javno izpostavljenih metod v aplikaciji pogosto najmanj zaupanja vredni, zato je njihovo preverjanje morda pomembnejše od preverjanja ničelnosti povprečnega predmeta.

Preverjanje parametrov metode za null je tudi podskupina splošnejše prakse preverjanja parametrov metode za splošno veljavnost, kot je razloženo v točki 38 druge izdaje Učinkovita Java (Točka 23 v prvi izdaji).

Razmislite o primitivcih in ne predmetih

Mislim, da ni dobro izbrati primitivne vrste podatkov (kot je int) nad ustreznim referenčnim tipom objekta (na primer Integer), da se izognemo možnosti NullPointerException, vendar ni mogoče zanikati, da je ena od prednosti primitivnih tipov ta, da ne vodijo do njih NullPointerExceptions. Vendar je treba še vedno preveriti veljavnost primitivov (mesec ne more biti negativno celo število), zato je ta korist lahko majhna. Po drugi strani pa primitivov ni mogoče uporabljati v zbirkah Java in včasih se zgodi, da se zahteva, da vrednost nastavi na nič.

Najpomembneje je biti zelo previden pri kombinaciji primitivov, referenčnih vrst in avtoboksa. V njem je opozorilo Učinkovita Java (Druga izdaja, postavka št. 49) glede nevarnosti, vključno z metanjem NullPointerException, povezane z neprevidnim mešanjem primitivnih in referenčnih vrst.

Previdno razmislite o klicih z verižno metodo

A NullPointerException je zelo enostavno najti, ker številka vrstice navaja, kje se je zgodila. Na primer, sled sklada je lahko videti tako, kot je prikazano naprej:

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

Sled sklada jasno kaže, da NullPointerException je bila vržena zaradi kode, izvedene v vrstici 222 od AvoidingNullPointerExamples.java. Tudi s številko vrstice je še vedno težko zožiti, kateri objekt je ničen, če je v isti vrstici dostop do več predmetov z metodami ali polji.

Na primer izjava všeč someObject.getObjectA (). getObjectB (). getObjectC (). toString (); ima štiri možne klice, ki bi lahko vrgli NullPointerException dodeljena isti vrstici kode. Pri tem vam lahko pomaga razhroščevalnik, vendar lahko pride do situacij, ko je zaželeno zgoraj razbiti zgornjo kodo, tako da se vsak klic izvede v ločeni vrstici. To omogoča številki vrstice, ki je v sledu sklada, da zlahka navede, kateri točno klic je bil problem. Poleg tega olajša izrecno preverjanje vsakega predmeta za nulo. Na spodnji strani pa razbijanje kode poveča vrstico štetja kode (nekaterim je to pozitivno!) In morda ni vedno zaželeno, še posebej, če je zagotovo nobena od zadevnih metod nikoli ne bo nična.

Naj bodo NullPointerExceptions bolj informativni

V zgornjem priporočilu je bilo opozorilo skrbno razmisliti o uporabi verige klicev metode, predvsem zato, ker je imelo številko vrstice v sledu sklada za NullPointerException manj koristno kot sicer. Vendar je številka vrstice prikazana v sledu sklada šele, ko je bila koda sestavljena z vključeno zastavico za odpravljanje napak. Če je bil sestavljen brez odpravljanja napak, je sled sklada videti tako, kot je prikazan naslednji:

java.lang.NullPointerException na dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (neznan vir) na dustin.examples.AvoidingNullPointerExamples.main (neznan vir) 

Kot dokazuje zgornji izhod, obstaja ime metode, ne pa tudi številke vrstice za NullPointerException. To otežuje takojšnjo ugotovitev, kaj v kodi je privedlo do izjeme. Eden od načinov za reševanje tega je zagotavljanje kontekstnih informacij v katerem koli vrženem NullPointerException. Ta ideja se je pokazala že prej, ko je a NullPointerException je bil ujet in znova vržen z dodatnimi informacijami o kontekstu kot a IllegalArgumentException. Tudi če je izjema preprosto znova vržena kot druga NullPointerException s kontekstnimi informacijami je še vedno koristno. Informacije o kontekstu pomagajo osebi, ki odpravlja napake, da hitreje prepozna pravi vzrok težave.

Naslednji primer dokazuje to načelo.

končni koledar nullCalendar = null; poskusite {končni datum Datum = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s koristnimi podatki", nullPointer, System.out); } poskusite {if (nullCalendar == null) {vrzi novo NullPointerException ("Ni bilo mogoče izvleči datuma iz navedenega koledarja"); } končni datum Datum = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s koristnimi podatki", nullPointer, System.out); } 

Rezultat izvajanja zgornje kode je videti takole.

NAPAKA: NullPointerException je naletel med poskusom NullPointerException s koristnimi podatki java.lang.NullPointerException NAPAKA: NullPointerException je naletel med poskusom NullPointerException s koristnimi podatki java.lang.NullPointerException: datuma ni bilo mogoče izvleči iz predvidenega koledarja 
$config[zx-auto] not found$config[zx-overlay] not found