Programmēšana

Efektīva Java NullPointerException apstrāde

Lai uzzinātu, kas ir NullPointerException, nav vajadzīga liela Java izstrādes pieredze. Faktiski viena persona ir uzsvērusi, ka nodarbojas ar to kā galveno kļūdu, ko pieļauj Java izstrādātāji. Es iepriekš blogoju par String.value (Object) izmantošanu, lai samazinātu nevēlamus NullPointerExceptions. Ir vairākas citas vienkāršas metodes, kuras var izmantot, lai samazinātu vai novērstu šī izplatītā RuntimeException veida parādīšanos, kas mums ir bijusi kopš JDK 1.0. Šajā emuāra ziņā apkopoti un apkopoti daži no populārākajiem no šiem paņēmieniem.

Pirms lietošanas pārbaudiet, vai katrs objekts nav tukšs

Visdrošākais veids, kā izvairīties no NullPointerException, ir pārbaudīt visas objekta atsauces, lai pārliecinātos, ka tās nav nulles, pirms piekļūstat vienam no objekta laukiem vai metodēm. Kā norāda nākamais piemērs, šī ir ļoti vienkārša tehnika.

final String causStr = "virknes pievienošana Deque, kas ir iestatīta uz nulli."; galīgais virknes elementsStr = "Fudd"; Deque deque = nulle; mēģiniet {deque.push (elementStr); žurnāls ("Veiksmīgi vietnē" + causStr, System.out); } catch (NullPointerException nullPointer) {log (causStr, nullPointer, System.out); } mēģiniet {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Veiksmīgi pie" + causStr + "(vispirms pārbaudot, vai nav nulles, un veicot Deque ieviešanu)", System.out); } catch (NullPointerException nullPointer) {log (causStr, nullPointer, System.out); } 

Iepriekš kodā izmantotais Deque tiek apzināti inicializēts uz nulli, lai atvieglotu piemēru. Kods pirmajā mēģiniet Pirms mēģināt piekļūt Deque metodei, bloks nepārbauda nulles līmeni. Otrajā kods mēģiniet bloks pārbauda, ​​vai nav nulles, un instantiates ievieš Deque (LinkedList), ja tas nav derīgs. Abu piemēru iznākums izskatās šādi:

KĻŪDA: NullPointerException radās, mēģinot pievienot virkni Deque, kas ir iestatīta uz nulli. java.lang.NullPointerException INFO: Veiksmīgi pievienojot virkni Deque, kas iestatīta uz nulli. (vispirms pārbaudot, vai nav nulles, un veicot Deque ieviešanu) 

Ziņojums, kas seko kļūdai iepriekš izvadē, norāda, ka a NullPointerException tiek izmests, kad mēģina izsaukt metodi Deque. Ziņojums, kas seko INFO augšējā izvadē, norāda, ka, pārbaudot Deque vispirms attiecībā uz nulli un pēc tam veicot jaunu ieviešanu, kad tā nav, izņēmums tika pilnībā izvairīts.

Šo pieeju bieži izmanto, un, kā parādīts iepriekš, tā var būt ļoti noderīga, lai izvairītos no nevēlamas (negaidītas) NullPointerException gadījumi. Tomēr tas nav bez izmaksām. Pirms katra objekta izmantošanas pārbaude par nulli var uzpūst kodu, var būt garlaicīgi rakstīt un paver vairāk iespēju problēmām ar papildu koda izstrādi un uzturēšanu. Šī iemesla dēļ ir runāts par Java valodas atbalsta ieviešanu iebūvētai nulles noteikšanai, šo pārbaudju automātiskai pievienošanai pēc sākotnējās kodēšanas, nulles drošiem veidiem, uz aspektu orientētas programmēšanas (AOP) izmantošanu, lai pievienotu nulles pārbaudi baitu kodu un citus nulles noteikšanas rīkus.

Groovy jau nodrošina ērtu mehānismu, kā rīkoties ar objektu atsaucēm, kas potenciāli nav derīgas. Groovy drošas navigācijas operators (?.) atgriež nulli, nevis met a NullPointerException kad piekļūst nulles objekta atsaucei.

Tā kā katra objekta atsauces nulles pārbaude var būt garlaicīga un uzpūš kodu, daudzi izstrādātāji izvēlas saprātīgi atlasīt objektus, kuros pārbaudīt nulli. Tas parasti noved pie nulles pārbaudes visiem potenciāli nezināmas izcelsmes objektiem. Ideja ir tāda, ka objektus var pārbaudīt atklātās saskarnēs, un pēc tam tiek pieņemts, ka pēc sākotnējās pārbaudes tie ir droši.

Šī ir situācija, kad trīskāršais operators var būt īpaši noderīgs. Tā vietā

// izguva BigDecimal, ko sauc par someObject String returnString; if (someObject! = null) {returnString = someObject.toEngineeringString (); } else {returnString = ""; } 

trīskāršais operators atbalsta šo kodolīgāko sintaksi

// izguva BigDecimal, ko sauc par someObject final String returnString = (someObject! = null)? someObject.toEngineeringString (): ""; } 

Pārbaudiet metodes argumentus par Null

Tikko apspriesto tehniku ​​var izmantot visiem priekšmetiem. Kā norādīts šīs tehnikas aprakstā, daudzi izstrādātāji izvēlas objektus pārbaudīt tikai tad, ja tie nāk no "neuzticamiem" avotiem. Tas bieži nozīmē, ka vispirms tiek pārbaudīts, vai nav nulles, izmantojot metodes, kas pakļautas ārējiem zvanītājiem. Piemēram, noteiktā klasē izstrādātājs var izvēlēties pārbaudīt, vai visos objektos, kas nodoti, nav nulles publiski metodes, bet nepārbaudiet, vai tajā nav nulles Privāts metodes.

Šis kods parāda šo pārbaudi, vai metode ievadot nav derīga. Tas ietver vienu metodi kā demonstratīvu metodi, kas pagriežas un izsauc divas metodes, nododot katrai metodei vienu nulles argumentu. Viena no metodēm, kas saņem nulles argumentu, vispirms pārbauda šo argumentu par nulli, bet otra tikai pieņem, ka ievadītais parametrs nav nulle.

 / ** * Pievienojiet iepriekš definētu tekstu String norādītajam StringBuilder. * * @param builder StringBuilder, kuram būs pievienots teksts; jābūt * nullei. * @throws IllegalArgumentException izmests, ja norādītais StringBuilder ir * nulle. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (final StringBuilder builder) {if (builder == null) {thrown new IllegalArgumentException ("Nodrošinātā StringBuilder bija nulle; jānorāda ne-null vērtība."); } builder.append ("Paldies, ka piegādājāt StringBuilder."); } / ** * Pievienojiet iepriekš definētu teksta virkni pievienotajam StringBuilder. * * @param builder StringBuilder, kuram būs pievienots teksts; jābūt * nullei. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (pēdējais StringBuilder celtnieks) {builder.append ("Paldies par StringBuilder piegādi."); } / ** * Pirms mēģināt izmantot * ievadītos parametrus, kas potenciāli ir nulle, parādiet parametru nulles pārbaudes efektivitāti. * / public void demonstrCheckingArgumentsForNull () {final String causStr = "nodrošināt nulli metodei kā argumentu."; logHeader ("DEMONSTRATING CHECKING METHOD PARAMETERS NULL", System.out); mēģiniet {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } catch (NullPointerException nullPointer) {log (causStr, nullPointer, System.out); } mēģiniet {appendPredefinedTextToProvidedBuilderCheckForNull (null); } catch (IllegalArgumentException nelegālsArgument) {žurnāls (causStr, nelegālsArgument, System.out); }} 

Kad iepriekšējais kods ir izpildīts, izeja parādās kā parādīts tālāk.

KĻŪDA: NullPointerException radās, mēģinot norādīt argumentam nulles metodi. java.lang.NullPointerException KĻŪDA: IllegalArgumentException radās, mēģinot norādīt argumentam nulles metodi. java.lang.IllegalArgumentException: sniegtais StringBuilder nebija derīgs; jānorāda vērtība, kas nav nulle. 

Abos gadījumos tika reģistrēts kļūdas ziņojums. Tomēr gadījums, kurā tika pārbaudīts nulles punkts, izmeta reklamēto IllegalArgumentException, kas ietvēra papildu konteksta informāciju par to, kad radās null. Alternatīvi šo nulles parametru varēja apstrādāt dažādos veidos. Gadījumā, kad ar nulles parametru netika apstrādāts, nebija iespēju, kā ar to rīkoties. Daudzi cilvēki dod priekšroku iemest a NullPolinterException ar papildu konteksta informāciju, kad ir skaidri atklāta nulles vērtība (skat. 60 Efektīva Java vai Nr. 42 pirmajā izdevumā), bet man ir neliela priekšroka NelegālsArgumentException ja tas ir tieši metodes arguments, kas ir nulle, jo es domāju, ka pats izņēmums pievieno konteksta detaļas, un tēmā ir viegli iekļaut "null".

Metodes, kā pārbaudīt nulles metožu argumentus, patiešām ir apakškopa vispārīgākai tehnikai, kā pārbaudīt visu objektu nulli. Tomēr, kā minēts iepriekš, lietojumprogrammā bieži tiek vismazāk uzticēti argumenti publiski atklātām metodēm, tāpēc to pārbaude var būt svarīgāka nekā vidējā objekta nulles pārbaude.

Metodes parametru pārbaude attiecībā uz nulli ir arī apakškopa vispārīgākai metodes parametru pārbaudei attiecībā uz vispārēju derīgumu, kā apspriests Efektīva Java (Pirmā izdevuma 23. punkts).

Apsveriet primitīvus, nevis objektus

Es nedomāju, ka ir laba ideja izvēlēties primitīvu datu tipu (piemēram, int) virs attiecīgā objekta atsauces veida (piemēram, Integer), lai vienkārši izvairītos no a NullPointerException, taču nevar noliegt, ka viena no primitīvo tipu priekšrocībām ir tā, ka pie tiem nenoved NullPointerExceptions. Tomēr primitīvu derīgums joprojām jāpārbauda (mēnesis nevar būt negatīvs vesels skaitlis), un tāpēc šis ieguvums var būt mazs. No otras puses, primitīvus nevar izmantot Java kolekcijās, un dažreiz cilvēks vēlas iespēju iestatīt vērtību uz nulli.

Vissvarīgākais ir būt ļoti piesardzīgam attiecībā uz primitīvu, atsauces veidu un autoboxing kombināciju. Iekšpusē ir brīdinājums Efektīva Java (Otrā izdevuma 49. pozīcija) attiecībā uz briesmām, tostarp NullPointerException, kas saistīts ar neuzmanīgu primitīvu un atsauces veidu sajaukšanu.

Rūpīgi apsveriet aicinājumus uz ķēdes metodi

A NullPointerException var būt ļoti viegli atrast, jo līnijas numurs norāda, kur tas notika. Piemēram, kaudzes izsekošana var izskatīties šādi:

java.lang.NullPointerException vietnē dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (AvoidingNullPointerExamples.java:222) vietnē dustin.examples.AvoidingNullPointerExamx.xamx. 

Steka izsekošana padara acīmredzamu, ka NullPointerException tika izmests koda rezultātā, kas izpildīts 2003. gada 222. rindā Izvairīšanās noNullPointerExamples.java. Pat ar norādīto līnijas numuru joprojām var būt grūti sašaurināt, kurš objekts ir nulle, ja vienā un tajā pašā rindā ir vairāki objekti ar metodēm vai laukiem.

Piemēram, paziņojums patīk someObject.getObjectA (). getObjectB (). getObjectC (). toString (); ir četri iespējamie zvani, kas, iespējams, ir iemetuši NullPointerException attiecina uz to pašu koda rindu. Atkļūdotāja izmantošana var palīdzēt šajā situācijā, taču var būt situācijas, kad ieteicams vienkārši sadalīt iepriekš minēto kodu tā, lai katrs zvans tiktu veikts uz atsevišķas līnijas. Tas ļauj līnijas numuram, kas atrodas kaudzes izsekojumā, viegli norādīt, kurš tieši zvans bija problēma. Turklāt tas atvieglo skaidru katra objekta pārbaudi, vai tajā nav datu. Tomēr trūkums ir tas, ka koda sadalīšana palielina koda rindu (dažiem tas ir pozitīvs!) Un ne vienmēr var būt vēlams, it īpaši, ja ir pārliecība, ka neviena no attiecīgajām metodēm nekad nebūs null.

Padariet NullPointerExceptions informatīvākus

Iepriekš minētajā ieteikumā brīdinājums bija rūpīgi apsvērt zvanu ķēdes metodes izmantošanu galvenokārt tāpēc, ka tas lika līnijas numuram atrasties kaudzē NullPointerException mazāk noderīga, nekā tas varētu būt citādi. Tomēr rindas numurs tiek parādīts kaudzes izsekojumā tikai tad, kad kods tika apkopots, ieslēdzot atkļūdošanas karodziņu. Ja tas tika kompilēts bez atkļūdošanas, kaudzes izsekošana izskatās šādi:

java.lang.NullPointerException vietnē dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (nezināms avots) vietnē dustin.examples.AvoidingNullPointerExamples.main (nezināms avots) 

Kā pierāda iepriekš minētā izeja, ir metodes nosaukums, bet rindas numurs nav NullPointerException. Tas apgrūtina tūlītēju identificēšanu, kas kodā izraisīja izņēmumu. Viens no veidiem, kā to novērst, ir sniegt konteksta informāciju jebkurā iemestajā NullPointerException. Šī ideja tika demonstrēta agrāk, kad a NullPointerException tika noķerts un atkal izmests ar papildu konteksta informāciju kā NelegālsArgumentException. Tomēr, pat ja izņēmums tiek vienkārši atkārtoti iemests kā cits NullPointerException ar konteksta informāciju tas joprojām ir noderīgs. Konteksta informācija palīdz koda atkļūdotājam ātrāk noteikt patieso problēmas cēloni.

Šis princips ir parādīts nākamajā piemērā.

galīgais kalendārs nullCalendar = null; mēģiniet {final Date date = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException ar noderīgiem datiem", nullPointer, System.out); } mēģiniet {if (nullCalendar == null) {mest jaunu NullPointerException ("Nevarēja izgūt datumu no piedāvātā kalendāra"); } galīgais datums datums = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException ar noderīgiem datiem", nullPointer, System.out); } 

Iepriekš minētā koda izpildes rezultāts izskatās šādi.

KĻŪDA: NullPointerException radās, mēģinot NullPointerException ar noderīgiem datiem java.lang.NullPointerException Kļūda: NullPointerException radās, mēģinot NullPointerException ar noderīgiem datiem java.lang.NullPointerException sniedza: Kalendāru nevarēja izvilkt no 
$config[zx-auto] not found$config[zx-overlay] not found