Programování

Efektivní zpracování Java NullPointerException

Netrvá mnoho zkušeností s vývojem prostředí Java, abyste se z první ruky naučili, o čem je NullPointerException. Ve skutečnosti jedna osoba zdůraznila řešení tohoto problému jako chybu číslo jedna, kterou vývojáři Java dělají. Dříve jsem blogoval o použití String.value (Object) ke snížení nežádoucích NullPointerExceptions. Existuje několik dalších jednoduchých technik, které lze použít ke snížení nebo eliminaci výskytů tohoto běžného typu RuntimeException, který je u nás od JDK 1.0. Tento příspěvek na blogu shrnuje a shrnuje některé z nejpopulárnějších z těchto technik.

Před použitím zkontrolujte, zda není u každého objektu hodnota Null

Nejjistější způsob, jak se vyhnout NullPointerException, je zkontrolovat všechny odkazy na objekty a zajistit, aby nebyly null před přístupem k jednomu z polí nebo metod objektu. Jak naznačuje následující příklad, jedná se o velmi jednoduchou techniku.

final String CauseStr = "přidání řetězce do Deque, který je nastaven na null."; final String elementStr = "Fudd"; Deque deque = null; zkuste {deque.push (elementStr); log ("Úspěšné v" + CauseStr, System.out); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } zkuste {if (deque == null) {deque = new LinkedList (); } deque.push (elementStr); log ("Úspěšné na" + CauseStr + "(nejprve zkontrolovat null a vytvořit instanci implementace Deque)", System.out); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } 

Ve výše uvedeném kódu je použitý Deque záměrně inicializován na null, aby se usnadnil příklad. Kód v první Snaž se block nekontroluje null před pokusem o přístup k metodě Deque. Kód ve druhém Snaž se blok kontroluje hodnotu null a vytváří instanci implementace Deque (LinkedList), pokud je null. Výstup z obou příkladů vypadá takto:

CHYBA: NullPointerException došlo při pokusu o přidání řetězce do Deque, který je nastaven na null. java.lang.NullPointerException INFO: Úspěšné přidání řetězce do Deque, který je nastaven na null. (kontrolou nejprve null a instancí implementace Deque) 

Zpráva následující CHYBA na výstupu výše naznačuje, že a NullPointerException je vyvolána při pokusu o volání metody na null Deque. Zpráva následující INFO ve výstupu výše to označuje kontrolou Deque nejprve pro null a poté pro ni vytvořte novou implementaci, když je null, výjimce se úplně vyhnula.

Tento přístup se často používá a jak je uvedeno výše, může být velmi užitečný při předcházení nechtěným (neočekávaným) NullPointerException instance. Není to však bez nákladů. Kontrola null před použitím každého objektu může nafouknout kód, může být únavné psát a otevírá více prostoru pro problémy s vývojem a údržbou dalšího kódu. Z tohoto důvodu se hovoří o zavedení podpory jazyka Java pro vestavěnou detekci nuly, automatické přidání těchto kontrol na hodnotu null po počátečním kódování, typy bezpečné pro null, použití Aspect-Oriented Programming (AOP) pro přidání kontroly null na bajtový kód a další nástroje pro detekci nuly.

Groovy již poskytuje vhodný mechanismus pro práci s odkazy na objekty, které jsou potenciálně nulové. Provozovatel bezpečné navigace Groovy (?.) vrací null místo házení a NullPointerException při přístupu k odkazu na nulový objekt.

Protože kontrola null pro každý odkaz na objekt může být zdlouhavá a způsobí nafouknutí kódu, mnoho vývojářů se rozhodne uvážlivě vybrat, které objekty mají zkontrolovat null. To obvykle vede ke kontrole nuly na všech objektech potenciálně neznámého původu. Myšlenka zde spočívá v tom, že objekty lze kontrolovat na exponovaných rozhraních a po první kontrole je pak možné je považovat za bezpečné.

V této situaci může být ternární operátor obzvláště užitečný. Namísto

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

ternární operátor podporuje tuto stručnější syntaxi

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

Zkontrolujte argumenty metody na hodnotu Null

Právě diskutovanou techniku ​​lze použít na všechny objekty. Jak je uvedeno v popisu této techniky, mnoho vývojářů se rozhodlo kontrolovat objekty pouze na null, když pocházejí z „nedůvěryhodných“ zdrojů. To často znamená testování na null první věc v metodách vystavených externím volajícím. Například v konkrétní třídě se může vývojář rozhodnout zkontrolovat null na všech předaných objektech veřejnost metody, ale nekontrolovat null v soukromé metody.

Následující kód ukazuje tuto kontrolu null na vstupu metody. Zahrnuje jedinou metodu jako demonstrativní metodu, která se otočí a volá dvě metody, přičemž každé metodě předá jediný argument null. Jedna z metod přijímajících argument null nejprve zkontroluje tento argument na hodnotu null, ale druhá pouze předpokládá, že předaný parametr není null.

 / ** * Připojit předdefinovaný text String k zadanému StringBuilder. * * @param builder StringBuilder, k němuž bude připojen text; by měl být * nenulový. * @throws IllegalArgumentException Vyvoláno, pokud je zadaný StringBuilder * null. * / private void appendPredefinedTextToProvidedBuilderCheckForNull (final StringBuilder builder) {if (builder == null) {throw new IllegalArgumentException ("Poskytnutý StringBuilder měl hodnotu null; musí být uvedena hodnota null."); } builder.append ("Děkujeme za dodání StringBuilder."); } / ** * Připojit předdefinovaný text String k poskytnutému StringBuilder. * * @param builder StringBuilder, k němuž bude připojen text; by * nemělo mít hodnotu null. * / private void appendPredefinedTextToProvidedBuilderNoCheckForNull (konečný stavitel StringBuilder) {builder.append ("Děkujeme za poskytnutí StringBuilder."); } / ** * Demonstrovat účinek kontroly parametrů na hodnotu null před použitím * předaných parametrů, které jsou potenciálně null. * / public void demonstrovatCheckingArgumentsForNull () {final String CauseStr = "poskytnout metodě null jako argument."; logHeader ("DEMONSTRATING CHECKING METHOD PARAMETERS FOR NULL", System.out); try {appendPredefinedTextToProvidedBuilderNoCheckForNull (null); } catch (NullPointerException nullPointer) {log (CauseStr, nullPointer, System.out); } zkuste {appendPredefinedTextToProvidedBuilderCheckForNull (null); } catch (IllegalArgumentException nelegálníArgument) {log (příčinaStr, nelegálníArgument, System.out); }} 

Po provedení výše uvedeného kódu se výstup zobrazí, jak je znázorněno dále.

CHYBA: NullPointerException došlo při pokusu o poskytnutí hodnoty null metodě jako argumentu. java.lang.NullPointerException ERROR: IllegalArgumentException došlo při pokusu o poskytnutí null metodě jako argument. java.lang.IllegalArgumentException: Poskytnutý StringBuilder byl null; musí být uvedena nenulová hodnota. 

V obou případech byla zaznamenána chybová zpráva. Případ, ve kterém byla zkontrolována null, však hodil inzerovanou IllegalArgumentException, která zahrnovala další kontextové informace o tom, kdy došlo k null. Alternativně by tento parametr null mohl být zpracován různými způsoby. V případě, že nebyl zpracován nulový parametr, nebyly k dispozici žádné možnosti, jak s ním zacházet. Mnoho lidí dává přednost hodu NullPolinterException s dalšími kontextovými informacemi, když je explicitně objevena null (viz položka č. 60 ve druhém vydání z Efektivní Java nebo položka č. 42 v prvním vydání), ale mám trochu přednost IllegalArgumentException když je to výslovně argument metody, který je null, protože si myslím, že samotná výjimka přidává podrobnosti kontextu a je snadné zahrnout do předmětu „null“.

Technika kontroly argumentů metody pro null je ve skutečnosti podmnožinou obecnější techniky kontroly všech objektů na null. Jak je však uvedeno výše, argumenty k veřejně exponovaným metodám jsou v aplikaci často nejméně důvěryhodné, takže jejich kontrola může být důležitější než kontrola průměrného objektu na hodnotu null.

Kontrola parametrů metody pro hodnotu null je také podmnožinou obecnější praxe kontroly parametrů metody pro obecnou platnost, jak je popsána v položce č. 38 druhého vydání Efektivní Java (Položka 23 v prvním vydání).

Zvažte spíše primitivní než objekty

Nemyslím si, že je dobrý nápad vybrat primitivní datový typ (například int) nad odpovídajícím typem odkazu na objekt (například Integer) jednoduše proto, aby se zabránilo možnosti a NullPointerException, ale nelze popřít, že jednou z výhod primitivních typů je, že k nim nevede NullPointerExceptions. Platnost primitiv však stále musí být zkontrolována (měsíc nesmí být záporné celé číslo), takže tato výhoda může být malá. Na druhou stranu primitivy nelze použít v kolekcích Java a někdy je potřeba mít možnost nastavit hodnotu na null.

Nejdůležitější věcí je být velmi opatrný ohledně kombinace primitiv, referenčních typů a autoboxu. V systému je varování Efektivní Java (Druhé vydání, položka # 49) týkající se nebezpečí, včetně házení NullPointerExceptionsouvisející s neopatrným mícháním primitivních a referenčních typů.

Pečlivě zvažte zřetězené volání metod

A NullPointerException může být velmi snadné najít, protože číslo řádku bude uvádět, kde k němu došlo. Například trasování zásobníku může vypadat takto:

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

Díky trasování zásobníku je zřejmé, že NullPointerException byl vyvolán v důsledku kódu provedeného na řádku 222 z AvoidingNullPointerExamples.java. I při zadaném čísle řádku může být stále obtížné zúžit, který objekt má hodnotu null, pokud na stejném řádku existuje více objektů s metodami nebo poli.

Například prohlášení jako someObject.getObjectA (). getObjectB (). getObjectC (). toString (); má čtyři možné hovory, které by mohly vyhodit NullPointerException přiřazen ke stejnému řádku kódu. Použití ladicího programu vám s tím může pomoci, ale mohou nastat situace, kdy je lepší jednoduše rozdělit výše uvedený kód tak, aby se každé volání provádělo na samostatném řádku. To umožňuje číslu řádku obsaženému v trasování zásobníku snadno určit, které přesné volání bylo problémem. Dále usnadňuje explicitní kontrolu null každého objektu. Na druhou stranu, rozbití kódu zvyšuje řádek počtu kódů (pro některé je to pozitivní!) A nemusí být vždy žádoucí, zvláště pokud je jisté, že žádná z dotyčných metod nebude nikdy null.

Zvyšte informovanost NullPointerExceptions

Ve výše uvedeném doporučení bylo varováním pečlivě zvážit použití řetězení volání metod primárně proto, že to mělo mít číslo řádku v trasování zásobníku pro NullPointerException méně užitečné, než by to jinak mohlo být. Číslo řádku se však zobrazuje pouze v trasování zásobníku, když byl kód zkompilován se zapnutým příznakem ladění. Pokud bylo zkompilováno bez ladění, trasování zásobníku vypadá takto:

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

Jak ukazuje výše uvedený výstup, existuje název metody, ale ne číslo řádku pro NullPointerException. Díky tomu je obtížnější okamžitě identifikovat, co v kódu vedlo k výjimce. Jedním ze způsobů, jak to vyřešit, je poskytnout kontextové informace v každém vyvolaném NullPointerException. Tato myšlenka byla prokázána dříve, když a NullPointerException byl chycen a znovu hoden s dalšími kontextovými informacemi jako a IllegalArgumentException. I když je výjimka jednoduše znovu vyvolána jako další NullPointerException s kontextovými informacemi je to stále užitečné. Kontextové informace pomáhají osobě ladit kód, aby rychleji identifikovala skutečnou příčinu problému.

Následující příklad ukazuje tento princip.

final Calendar nullCalendar = null; try {final Date date = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s užitečnými daty", nullPointer, System.out); } vyzkoušet {if (nullCalendar == null) {hodit novou NullPointerException ("Nelze extrahovat datum z poskytnutého kalendáře"); } konečné datum datum = nullCalendar.getTime (); } catch (NullPointerException nullPointer) {log ("NullPointerException s užitečnými daty", nullPointer, System.out); } 

Výstup z běhu výše uvedeného kódu vypadá následovně.

CHYBA: NullPointerException došlo při pokusu o NullPointerException s užitečnými daty java.lang.NullPointerException CHYBA: NullPointerException došlo při pokusu o NullPointerException s užitečnými daty java.lang.NullPointerException: Nelze extrahovat datum z poskytnutého kalendáře 
$config[zx-auto] not found$config[zx-overlay] not found