Podczas czytania kodu źródłowego JDK często zdarza się, że autor sprawdza parametry, jeśli są one puste, a następnie ręcznie rzuca nowy wyjątek NullPointerException (). Dlaczego oni to robią? Myślę, że nie ma takiej potrzeby, ponieważ wywoła nową NullPointerException (), gdy wywoła dowolną metodę. (Oto przykładowy kod źródłowy HashMap :)
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
if (remappingFunction == null)
throw new NullPointerException();
Node<K,V> e; V oldValue;
int hash = hash(key);
if ((e = getNode(hash, key)) != null &&
(oldValue = e.value) != null) {
V v = remappingFunction.apply(key, oldValue);
if (v != null) {
e.value = v;
afterNodeAccess(e);
return v;
}
else
removeNode(hash, key, null, false, true);
}
return null;
}
java
nullpointerexception
LiJiaming
źródło
źródło
ArgumentNullException
w takich przypadkach (a nieNullReferenceException
) - w rzeczywistości jest to naprawdę dobre pytanie, dlaczego podnosiszNullPointerException
jawnie tutaj (zamiast innego).IllegalArgumentException
lubNullPointerException
o zerowy argument. Konwencja JDK jest tym drugim.Odpowiedzi:
Przychodzi mi na myśl szereg przyczyn, z których kilka jest ściśle powiązanych:
Szybka awaria: jeśli zawiedzie, najlepiej raczej zawieść wcześniej niż później. Umożliwia to wychwycenie problemów bliżej ich źródła, co ułatwia ich identyfikację i odzyskanie. Pozwala także uniknąć marnowania cykli procesora na kod, który z pewnością zawiedzie.
Zamiar: Zgłoszenie wyjątku wyraźnie mówi opiekunom, że błąd występuje tam celowo, a autor był świadomy konsekwencji.
Spójność: jeśli pozwolono, aby błąd wystąpił naturalnie, może nie wystąpić w każdym scenariuszu. Jeśli na przykład nie zostanie znalezione mapowanie,
remappingFunction
nigdy nie zostanie użyte, a wyjątek nie zostanie zgłoszony. Wcześniejsza weryfikacja danych wejściowych pozwala na bardziej deterministyczne zachowanie i bardziej przejrzystą dokumentację .Stabilność: Kod ewoluuje z czasem. Kod napotkany wyjątek w naturalny sposób może, po pewnym przeredagowaniu, przestać to robić lub zrobić to w różnych okolicznościach. Rzucanie go jawnie zmniejsza prawdopodobieństwo przypadkowej zmiany zachowania.
źródło
new NullPointerException(message)
konstruktora, aby wyjaśnić, co jest null. Dobry dla osób, które nie mają dostępu do Twojego kodu źródłowego. Zrobili to nawet jednowierszowe w JDK 8Objects.requireNonNull(object, message)
metodą użytkową.Ma to na celu zapewnienie jasności, spójności i zapobieganie dodatkowej, niepotrzebnej pracy.
Zastanów się, co by się stało, gdyby na szczycie metody nie było klauzuli ochronnej. Zawsze zadzwoni,
hash(key)
agetNode(hash, key)
nawet jeślinull
zostanie wydanyremappingFunction
przed wyrzuceniem NPE.Co gorsza, jeśli
if
warunek jest spełnionyfalse
, bierzemyelse
gałąź, która w ogóle nie używaremappingFunction
, co oznacza, że metoda nie zawsze wyrzuca NPE, gdynull
przejściu a; czy tak, zależy od stanu mapy.Oba scenariusze są złe. Jeśli
null
nie jest prawidłową wartością dlaremappingFunction
metoda powinna konsekwentnie zgłaszać wyjątek bez względu na wewnętrzny stan obiektu w momencie wywołania i powinna to robić bez wykonywania zbędnej pracy, która jest bezcelowa, biorąc pod uwagę, że po prostu rzuci. Wreszcie, dobrą zasadą czystego, przejrzystego kodu jest umieszczenie straży bezpośrednio, aby każdy, kto przejrzy kod źródłowy, mógł łatwo zobaczyć, że to zrobi.Nawet jeśli wyjątek byłby obecnie zgłaszany przez każdą gałąź kodu, możliwe jest, że przyszła wersja tego kodu to zmieni. Przeprowadzenie kontroli na początku zapewnia, że na pewno zostanie wykonana.
źródło
Oprócz powodów wymienionych przez doskonałą odpowiedź @ shmosel ...
Wydajność: Mogą występować / były korzyści z wydajności (w niektórych maszynach JVM) wynikające z jawnego rzucania NPE, a nie pozwalania JVM na to.
Zależy to od strategii, jaką interpreter Java i kompilator JIT stosują do wykrywania dereferencji wskaźników zerowych. Jedną strategią jest nie testowanie na wartość NULL, ale zamiast tego pułapkowanie SIGSEGV, które ma miejsce, gdy instrukcja próbuje uzyskać dostęp do adresu 0. Jest to najszybsze podejście w przypadku, gdy odwołanie jest zawsze ważne, ale jest drogie w przypadku NPE.
Wyraźny test
null
w kodzie pozwoliłby uniknąć trafienia wydajności SIGSEGV w scenariuszu, w którym NPE były częste.(Wątpię, czy byłaby to opłacalna mikrooptymalizacja w nowoczesnej maszynie JVM, ale mogło tak być w przeszłości).
Zgodność: prawdopodobnym powodem braku komunikatu w wyjątku jest zgodność z NPE generowanymi przez samą JVM. W zgodnej implementacji Java komunikat NPE wygenerowany przez JVM ma
null
komunikat. (Android Java jest inny.)źródło
Oprócz tego, co zauważyli inni ludzie, warto tutaj zwrócić uwagę na rolę konwencji. Na przykład w języku C # masz również tę samą konwencję jawnego zgłaszania wyjątku w takich przypadkach, ale jest to konkretna
ArgumentNullException
, która jest nieco bardziej szczegółowa. (Konwencja C # jest taka, żeNullReferenceException
zawsze reprezentuje jakiś rodzaj błędu - po prostu nie powinno się to nigdy zdarzać w kodzie produkcyjnym; oczywiścieArgumentNullException
, również, ale może to być błąd bardziej podobny do „nie zrozumieć, jak prawidłowo korzystać z biblioteki (rodzaj błędu).Zasadniczo więc w języku C #
NullReferenceException
oznacza, że twój program faktycznie próbował go użyć, podczasArgumentNullException
gdy oznacza to, że rozpoznał, że wartość była niepoprawna i nawet nie zadał sobie trudu, aby go użyć. Implikacje mogą być różne (w zależności od okoliczności), ponieważArgumentNullException
oznacza, że dana metoda nie miała jeszcze skutków ubocznych (ponieważ nie spełniła warunków wstępnych metody).Nawiasem mówiąc, jeśli podbijasz coś w stylu
ArgumentNullException
lubIllegalArgumentException
, jest to po prostu część sprawdzania: chcesz innego wyjątku, niż normalnie.Tak czy inaczej, jawne zgłoszenie wyjątku wzmacnia dobrą praktykę wyrażania się jasno na temat warunków wstępnych i oczekiwanych argumentów metody, co ułatwia czytanie, używanie i utrzymywanie kodu. Jeśli nie
null
zaznaczyłeś jawnie , nie wiem, czy to dlatego, że myślałeś, że nikt nigdy nie przejdzienull
argumentu, liczysz się z tym, że i tak rzuci wyjątek, albo po prostu zapomniałeś go sprawdzić.źródło
null
obiektu.” Nie szaleję za tym, ale wydaje się, że to zamierzony projekt.NullReferenceException
(odpowiednikNullPointerException
) oznacza, że twój kod faktycznie próbował go użyć (co zawsze jest błędem - nigdy nie powinno się zdarzyć w kodzie produkcyjnym), a „wiem, że argument jest błędny, więc nawet nie spróbuj go użyć ”. Jest teżArgumentException
(co oznacza, że argument był błędny z innego powodu).Jest tak, że otrzymasz wyjątek, gdy tylko popełnisz błąd, a nie później, gdy będziesz korzystać z mapy i nie zrozumiesz, dlaczego tak się stało.
źródło
Zmienia pozornie błędny warunek błędu w wyraźne naruszenie umowy: funkcja ma pewne warunki wstępne do poprawnego działania, więc sprawdza je wcześniej, wymuszając ich spełnienie.
Skutkuje to tym, że nie będziesz musiał debugować,
computeIfPresent()
gdy wyjmiesz z niego wyjątek. Gdy zobaczysz, że wyjątek pochodzi z kontroli warunków wstępnych, wiesz, że wywołałeś funkcję z niedozwolonym argumentem. Gdyby nie było czeku, należy wykluczyć możliwość, że istnieje w nim jakiś błądcomputeIfPresent()
, który prowadzi do zgłoszenia wyjątku.Oczywiście rzucenie
NullPointerException
leku generycznego jest naprawdę złym wyborem, ponieważ nie oznacza samo w sobie naruszenia umowy.IllegalArgumentException
byłby lepszym wyborem.Sidenote:
Nie wiem, czy Java na to pozwala (wątpię w to), ale programiści C / C ++ używają
assert()
w tym przypadku opcji, która jest znacznie lepsza do debugowania: Mówi programowi, aby zawiesił się natychmiast i tak mocno, jak to możliwe, jeśli pod warunkiem warunek oceniany na false. Więc jeśli uciekłeśpod debuggerem i coś przechodziło
NULL
do któregokolwiek argumentu, program zatrzymywałby się tuż przy liniiNULL
i wskazywał, który argument był , a ty mógłbyś w dowolnym momencie zbadać wszystkie lokalne zmienne całego stosu wywołań.źródło
assert something != null;
ale to wymaga-assertions
flagi podczas uruchamiania aplikacji. Jeśli-assertions
flagi nie ma, słowo kluczowe assert nie zgłosi wyjątku AssertionExceptionJest tak, ponieważ możliwe jest, że nie nastąpi to naturalnie. Zobaczmy taki kod:
(oczywiście w tej próbce jest wiele problemów z jakością kodu. Przykro mi, że wyobrażam sobie idealną próbkę)
Sytuacja, w której
c
nie będzie faktycznie używana iNullPointerException
nie zostanie wyrzucona, nawet jeślic == null
jest to możliwe.W bardziej skomplikowanych sytuacjach polowanie na takie przypadki staje się bardzo trudne. Właśnie dlatego ogólna kontrola jak
if (c == null) throw new NullPointerException()
jest lepsza.źródło
Celowo należy chronić dalsze szkody lub doprowadzić do niespójności.
źródło
Oprócz wszystkich innych doskonałych odpowiedzi tutaj, chciałbym również dodać kilka przypadków.
Możesz dodać wiadomość, jeśli utworzysz własny wyjątek
Jeśli rzucisz własny
NullPointerException
, możesz dodać wiadomość (co zdecydowanie powinieneś!)Komunikat domyślny jest to
null
odnew NullPointerException()
a wszystkie metody, które go użyć, na przykładObjects.requireNonNull
. Jeśli wydrukujesz tę wartość zerową, może ona nawet przekształcić się w pusty ciąg ...Trochę krótki i mało pouczający ...
Śledzenie stosu da wiele informacji, ale aby użytkownik mógł dowiedzieć się, która wartość jest pusta, musi wykopać kod i przyjrzeć się dokładnemu wierszowi.
Teraz wyobraź sobie, że NPE jest pakowane i wysyłane przez sieć, np. Jako komunikat w błędzie usługi internetowej, być może między różnymi działami, a nawet organizacjami. W najgorszym przypadku nikt nie może zrozumieć, co
null
oznacza ...Połączone wywołania metod będą Cię zgadywać
Wyjątek powie ci tylko w jakim wierszu wystąpił wyjątek. Rozważ następujący wiersz:
Jeśli pojawi się NPE i wskazuje w tym samym wierszu, co jeden
repository
isomeObject
była zerowa?Zamiast tego, sprawdzenie tych zmiennych, gdy je otrzymasz, przynajmniej wskaże wiersz, w którym, mam nadzieję, jedyną obsługiwaną zmienną. Jak wspomniano wcześniej, nawet lepiej, jeśli komunikat o błędzie zawiera nazwę zmiennej lub podobną.
Błędy podczas przetwarzania dużej ilości danych wejściowych powinny zawierać informacje identyfikujące
Wyobraź sobie, że twój program przetwarza plik wejściowy z tysiącami wierszy i nagle pojawia się wyjątek NullPointerException. Patrzysz na to miejsce i zdajesz sobie sprawę, że niektóre dane wejściowe były nieprawidłowe ... jakie dane wejściowe? Potrzebujesz więcej informacji na temat numeru wiersza, być może kolumny lub nawet całego tekstu wiersza, aby zrozumieć, który wiersz w tym pliku wymaga naprawy.
źródło