Programovanie

Efektívne spracovanie Java NullPointerException

Na to, aby ste sa na vlastnej koži dozvedeli, o čom je NullPointerException, nie je potrebných veľa skúseností s vývojom Java. Jedna osoba v skutočnosti zdôraznila riešenie tohto problému ako chybu číslo jedna, ktorú vývojári Java robia. Predtým som blogoval o použití String.value (Object) na zníženie nežiaducich NullPointerExceptions. Existuje niekoľko ďalších jednoduchých techník, ktoré je možné použiť na zníženie alebo elimináciu výskytu tohto bežného typu RuntimeException, ktorý je s nami od JDK 1.0. Tento blogový príspevok zhromažďuje a sumarizuje niektoré z najpopulárnejších z týchto techník.

Pred použitím skontrolujte každý objekt na hodnotu Null

Najbezpečnejším spôsobom, ako sa vyhnúť NullPointerException, je skontrolovať všetky odkazy na objekty, aby ste sa ubezpečili, že nie sú null pred prístupom k jednému z polí alebo metód objektu. Ako naznačuje nasledujúci príklad, jedná sa o veľmi jednoduchú techniku.

final String CauseStr = "pridanie reťazca do Deque, ktorý je nastavený na null."; final String elementStr = "Fudd"; Deque deque = null; skus {deque.push (elementStr); log ("Úspešné o" + CauseStr, System.out); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } skus {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Úspešné o" + CauseStr + "(najskôr skontrolujte null a vytvorte inštanciu implementácie Deque)", System.out); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } 

V kóde vyššie je použitý Deque zámerne inicializovaný na hodnotu null, aby sa uľahčil príklad. Kód v prvom skús blok nekontroluje hodnotu null pred pokusom o prístup k metóde Deque. Kód v druhej skús blok skontroluje, či nie je null, a vytvorí inštanciu implementácie súboru Deque (LinkedList), ak je nulový. Výstup z oboch príkladov vyzerá takto:

CHYBA: NullPointerException sa vyskytla pri pokuse o pridanie reťazca do Deque, ktorý je nastavený na hodnotu null. java.lang.NullPointerException INFO: Úspešné pri pridávaní reťazca do Deque, ktorý je nastavený na hodnotu null. (kontrolou najskôr nuly a vytvorením inštancie implementácie Deque) 

Správa nasledujúca po CHYBE na výstupe vyššie naznačuje, že a NullPointerException sa vyvolá pri pokuse o volanie metódy na hodnotu null Deque. Správa nasledujúca po INFO vo výstupe vyššie to naznačuje kontrolou Deque najskôr pre null a potom pre ňu vytvoriť inštanciu novej implementácie, keď je nulová, výnimke sa úplne vyhlo.

Tento prístup sa často používa a ako je uvedené vyššie, môže byť veľmi užitočný pri predchádzaní nežiaducim (neočakávaným) javom. NullPointerException inštancie. Nie je to však bez nákladov. Kontrola nulovej hodnoty pred použitím každého objektu môže nafúknuť kód, môže byť namáhavé písať a otvára viac priestoru pre problémy s vývojom a údržbou dodatočného kódu. Z tohto dôvodu sa hovorilo o zavedení podpory jazyka Java pre vstavanú detekciu nuly, automatického pridávania týchto kontrol nuly po počiatočnom kódovaní, typov bezpečných pre nuly, použitia Aspect-Oriented Programming (AOP) na pridanie kontroly nuly na bajtový kód a ďalšie nástroje na detekciu nuly.

Groovy už poskytuje vhodný mechanizmus na prácu s referenciami na objekty, ktoré sú potenciálne nulové. Operátor bezpečnej navigácie Groovy (?.) vráti null namiesto hodu a NullPointerException keď je sprístupnený odkaz na nulový objekt.

Pretože kontrola hodnoty null pre každý odkaz na objekt môže byť zdĺhavá a nafukuje kód, mnohí vývojári sa rozhodli uvážlivo vybrať, ktoré objekty majú skontrolovať hodnotu null. To zvyčajne vedie ku kontrole nuly na všetkých objektoch potenciálne neznámeho pôvodu. Myšlienka spočíva v tom, že objekty je možné skontrolovať na exponovaných rozhraniach a potom sa po počiatočnej kontrole považuje za bezpečné.

V tejto situácii môže byť ternárny operátor obzvlášť užitočný. Namiesto

// načítal BigDecimal s názvom someObject String returnString; if (someObject! = null) {returnString = someObject.toEngineeringString (); } else {returnString = ""; } 

ternárny operátor podporuje túto stručnejšiu syntax

// načítal BigDecimal s názvom someObject final String returnString = (someObject! = null)? someObject.toEngineeringString (): ""; } 

Skontrolujte argumenty metódy na hodnotu Null

Práve diskutovanú techniku ​​je možné použiť na všetky objekty. Ako sa uvádza v popise tejto techniky, mnoho vývojárov sa rozhodlo skontrolovať objekty na hodnotu null iba vtedy, keď pochádzajú z „nedôveryhodných“ zdrojov. To často znamená testovanie nulovej prvej veci v metódach vystavených externým volajúcim. Napríklad v konkrétnej triede sa môže vývojár rozhodnúť skontrolovať hodnotu null na všetkých objektoch odovzdaných verejné metódy, ale nekontrolujte null v súkromné metódy.

Nasledujúci kód demonštruje túto kontrolu na hodnotu null pri zadávaní metódy. Zahŕňa jednu metódu ako demonštratívnu metódu, ktorá obracia a volá dve metódy, pričom každej metóde odovzdáva jeden nulový argument. Jedna z metód prijímajúcich argument null najskôr skontroluje, či je argument null, ale druhá iba predpokladá, že zadaný parameter nie je null.

 / ** * Pripojiť preddefinovaný text String k poskytnutému StringBuilder. * * @param builder StringBuilder, ku ktorému bude pripojený text; by * nemal mať hodnotu null. * @throws IllegalArgumentException Vyvolá sa, ak je zadaný StringBuilder * null. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (konečný staviteľ StringBuilder) {if (builder == null) {hodiť novú IllegalArgumentException ("Zadaný StringBuilder bol null; musí byť poskytnutá hodnota null."); } builder.append ("Ďakujeme za dodanie StringBuilder."); } / ** * Pripojiť preddefinovaný text String k poskytnutému StringBuilder. * * @param builder StringBuilder, ku ktorému bude pripojený text; by * nemal mať hodnotu null. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (konečný staviteľ StringBuilder) {builder.append ("Ďakujeme za dodanie StringBuilder."); } / ** * Ukážte účinok kontroly parametrov na hodnotu null pred pokusom o použitie * vložených parametrov, ktoré sú potenciálne nulové. * / public void demonstrovatCheckingArgumentsForNull () {final String CauseStr = "poskytnúť metóde null ako argument."; logHeader ("PREUKAZOVANIE PARAMETROV KONTROLNEJ METÓDY PRE NULL", System.out); try {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } skus {appendPredefinedTextToProvidedBuilderCheckForNull (null); } catch (IllegalArgumentException legalArgument) {log (CauseStr, legalArgument, System.out); }} 

Po vykonaní vyššie uvedeného kódu sa výstup zobrazí, ako je znázornené ďalej.

CHYBA: NullPointerException sa vyskytla pri pokuse o poskytnutie hodnoty null metóde ako argumentu. java.lang.NullPointerException CHYBA: IllegalArgumentException sa vyskytla pri pokuse o poskytnutie metódy null ako argumentu. java.lang.IllegalArgumentException: Poskytnutý StringBuilder bol null; musí byť uvedená nenulová hodnota. 

V obidvoch prípadoch bola zaznamenaná chybová správa. Prípad, v ktorom sa skontrolovala hodnota null, však vyhodil inzerovanú IllegalArgumentException, ktorá obsahovala ďalšie kontextové informácie o tom, kedy došlo k hodnote null. Alternatívne by sa s týmto nulovým parametrom dalo zaobchádzať rôznymi spôsobmi. V prípade, že sa nespracoval nulový parameter, neexistovali žiadne možnosti, ako s ním zaobchádzať. Mnoho ľudí radšej hodí a NullPolinterException s dodatočnými kontextovými informáciami, keď je explicitne zistená hodnota null (pozri položku č. 60 v druhom vydaní z Efektívna Java alebo Položka # 42 v prvom vydaní), ale mám mierne prednosť pre IllegalArgumentException keď je to výslovne argument metódy, ktorý má hodnotu null, pretože si myslím, že samotná výnimka pridá podrobnosti kontextu a je ľahké do predmetu zahrnúť slovo „null“.

Technika kontroly argumentov metódy na hodnotu null je skutočne podmnožinou všeobecnejšej techniky kontroly na hodnotu null všetkých objektov. Ako je však uvedené vyššie, argumentom pre verejne exponované metódy sa v aplikácii často nedôveruje najmenej, a preto ich kontrola môže byť dôležitejšia ako kontrola nulového priemeru objektu.

Kontrola parametrov metódy na hodnotu null je tiež podmnožinou všeobecnejšej praxe kontroly parametrov metódy so všeobecnou platnosťou, ako je popísaná v položke č. 38 druhého vydania Efektívna Java (Položka 23 v prvom vydaní).

Zvážte skôr primitívy ako objekty

Nemyslím si, že je dobrý nápad vyberať primitívny dátový typ (napr int) nad príslušným referenčným typom objektu (napríklad Integer), aby sa zabránilo možnosti a NullPointerException, ale nemožno poprieť, že jednou z výhod primitívnych typov je, že k nim nevedú NullPointerExceptions. Primitívnosť je však stále potrebné skontrolovať z hľadiska platnosti (mesiac nemôže byť záporné celé číslo), a tak môže byť táto výhoda malá. Na druhej strane primitívne prvky nemožno použiť v zbierkach Java a niekedy je potrebné nastaviť hodnotu na nulu.

Najdôležitejšie je byť veľmi opatrný pri kombinovaní primitívov, referenčných typov a autoboxu. Vo vnútri je varovanie Efektívna Java (Druhé vydanie, položka # 49) týkajúce sa nebezpečenstiev vrátane vrhania NullPointerException, súvisiace s neopatrným miešaním primitívnych a referenčných typov.

Starostlivo zvážte reťazené volania metód

A NullPointerException možno nájsť veľmi ľahko, pretože číslo riadku bude uvádzať, kde k nemu došlo. Napríklad stopa zásobníka môže vyzerať takto:

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

Zo stopy zásobníka je zrejmé, že NullPointerException bol vyhodený v dôsledku kódu vykonaného na riadku 222 z AvoidingNullPointerExamples.java. Aj napriek uvedenému číslu riadku môže byť stále ťažké zúžiť, ktorý objekt je nulový, ak na rovnakom riadku existuje viac objektov s metódami alebo poľami.

Napríklad vyhlásenie ako someObject.getObjectA (). getObjectB (). getObjectC (). toString (); má štyri možné hovory, ktoré by mohli vyhodiť NullPointerException rovnaký riadok kódu. S tým vám môže pomôcť použitie debuggeru, ale môžu nastať situácie, kedy je lepšie uvedený kód jednoducho rozdeliť, aby sa každé volanie uskutočňovalo na samostatnom riadku. To umožňuje číslu riadku obsiahnutému v sledovaní zásobníka ľahko určiť, ktoré presné volanie bolo problémom. Ďalej uľahčuje explicitnú kontrolu nulového stavu každého objektu. Avšak na druhú stranu, rozbitie kódu zvýši počet riadkov v kóde (pre niektorých je to pozitívne!) A nemusí byť vždy žiaduce, najmä ak je isté, že žiadna z predmetných metód nebude nikdy neplatná.

Zvýšte informovanosť NullPointerExceptions

Vo vyššie uvedenom odporúčaní bolo varovaním starostlivo zvážiť použitie reťazenia hovorov metódami predovšetkým preto, že sa pri sledovaní zásobníka pre NullPointerException menej užitočné, ako by to inak mohlo byť. Číslo riadku sa však v trasovaní zásobníka zobrazuje iba vtedy, keď bol kód kompilovaný so zapnutým príznakom ladenia. Ak to bolo skompilované bez ladenia, trasovanie zásobníka vyzerá takto:

java.lang.NullPointerException na dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace (neznámy zdroj) na dustin.examples.AvoidingNullPointerExamples.main (neznámy zdroj) 

Ako ukazuje vyššie uvedený výstup, existuje názov metódy, ale nie číslo riadku pre NullPointerException. To sťažuje okamžitú identifikáciu toho, čo v kóde viedlo k výnimke. Jedným zo spôsobov, ako to vyriešiť, je poskytnúť kontextové informácie o každom hodenom NullPointerException. Táto myšlienka sa demonštrovala už skôr, keď a NullPointerException bol chytený a znovu vyhodený s ďalšími kontextovými informáciami ako a IllegalArgumentException. Avšak aj keď sa výnimka jednoducho znovu hodí ako ďalšia NullPointerException s kontextovými informáciami je to stále užitočné. Kontextové informácie pomáhajú osobe ladiacej kód rýchlejšie identifikovať skutočnú príčinu problému.

Nasledujúci príklad demonštruje tento princíp.

final Calendar nullCalendar = null; try {final Date date = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s užitočnými údajmi", nullPointer, System.out); } try {if (nullCalendar == null) {throw new NullPointerException ("Could not extract Date from provided Calendar"); } konečný dátum dátum = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s užitočnými údajmi", nullPointer, System.out); } 

Výstup zo spustenia vyššie uvedeného kódu vyzerá nasledovne.

CHYBA: NullPointerException došlo pri pokuse o NullPointerException s užitočnými údajmi java.lang.NullPointerException CHYBA: NullPointerException došlo pri pokuse o NullPointerException s užitočnými údajmi java.lang.NullPointerException: Nepodarilo sa extrahovať dátum z poskytnutého kalendára 
$config[zx-auto] not found$config[zx-overlay] not found