Zauważyłem, że wiele metod Java 8 w Oracle JDK używa Objects.requireNonNull()
, które wewnętrznie wyrzuca, NullPointerException
jeśli dany obiekt (argument) jest null
.
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
Ale i NullPointerException
tak zostanie wyrzucony, jeśli null
obiekt zostanie zaniedbany. Dlaczego więc należy wykonać tę dodatkową kontrolę zerową i rzucić
NullPointerException
?
Jedną oczywistą odpowiedzią (lub korzyścią) jest to, że czyni kod bardziej czytelnym i zgadzam się. Chciałbym poznać wszelkie inne powody używania
Objects.requireNonNull()
na początku metody.
java
java-8
nullpointerexception
użytkownik4686046
źródło
źródło
Objects.requireNonNull
, twój kod nie ma rozgałęzień, więc pojedynczy test jednostkowy zapewni 100% pokrycia :-)Odpowiedzi:
Ponieważ robiąc to, możesz wyrazić to jasno . Lubić:
Lub krócej:
Teraz wiesz :
new()
Porównaj to z: dzisiaj tworzysz obiekt Foo, a jutro wywołujesz metodę, która używa tego pola i rzuca. Najprawdopodobniej jutro nie dowiesz się, dlaczego to odniesienie było wczoraj puste, kiedy zostało przekazane konstruktorowi!
Innymi słowy: używając jawnie tej metody do sprawdzania przychodzących referencji, możesz kontrolować moment, w którym wyjątek zostanie zgłoszony. I przez większość czasu chcesz zawieść tak szybko, jak to możliwe !
Główne zalety to:
bar
nie ma wartości zerowej - a zatem nie potrzebujesz żadnychif (bar == null)
kontroli w innych miejscach!źródło
this.bar = Objects.requireNonNull(bar, "bar must not be null");
Objects.requireNonNull(bar, "bar must not be null");
. Dzięki za to.this.bar = Objects.requireNonNull(bar, "bar must not be null");
to poprawne w konstruktorach, ale potencjalnie niebezpieczne w innych metodach, jeśli dwie lub więcej zmiennych jest ustawionych w tej samej metodzie. Przykład: talkwards.com/2018/11/03/…Niezawodny
Kod powinien ulec awarii tak szybko, jak to możliwe. Nie powinno to wykonywać połowy pracy, a następnie oderwać się od wartości zerowej i powodować awarię tylko wtedy, gdy połowa wykonanej pracy powoduje, że system jest w nieprawidłowym stanie.
Jest to powszechnie nazywane „wczesnym niepowodzeniem” lub „szybkim działaniem awaryjnym” .
źródło
Oznacza to, że natychmiast i niezawodnie wykryjesz problem .
Rozważać:
.NET poprawia to poprzez oddzielenie
NullReferenceException
(„wyrejestrowałeś wartość zerową”) odArgumentNullException
(„nie powinieneś podawać wartości null jako argumentu - i tak było w przypadku tego parametru). Chciałbym, aby Java zrobiła to samo, ale nawet z tylkoNullPointerException
, że to nadal dużo łatwiejsze do kodu poprawek, jeżeli zostanie zgłoszony błąd w najbliższym punkcie, w którym może zostać wykryte.źródło
Użycie
requireNonNull()
jako pierwszych instrukcji w metodzie pozwala na natychmiastowe zidentyfikowanie przyczyny wyjątku.Stacktrace wyraźnie wskazuje, że wyjątek został zgłoszony natychmiast po wprowadzeniu metody, ponieważ program wywołujący nie przestrzegał wymagań / kontraktu. Przekazanie
null
obiektu do innej metody może rzeczywiście wywołać wyjątek na raz, ale przyczyna problemu może być bardziej skomplikowana do zrozumienia, ponieważ wyjątek zostanie zgłoszony w konkretnym wywołaniunull
obiektu, które może być znacznie dalej.Oto konkretny i prawdziwy przykład, który pokazuje, dlaczego ogólnie musimy sprzyjać szybkiemu niepowodzeniu, a w szczególności za pomocą
Object.requireNonNull()
lub w jakikolwiek sposób wykonać kontrolę zerową parametrów zaprojektowanych tak, by nie byłynull
.Załóżmy, że
Dictionary
klasa, która komponujeLookupService
iList
odString
reprezentujący słów zawartych w. Pola te są zaprojektowane, aby nie byćnull
, a jednym z nich jest przekazywana wDictionary
konstruktorze.Załóżmy teraz, że „zła” implementacja
Dictionary
beznull
sprawdzania we wpisie metody (tutaj jest to konstruktor):Teraz wywołajmy
Dictionary
konstruktor znull
referencją dowords
parametru:JVM rzuca NPE na tę instrukcję:
Wyjątek jest wyzwalany w
LookupService
klasie, podczas gdy jego pochodzenie jest znacznie wcześniej (Dictionary
konstruktor). To sprawia, że ogólna analiza problemu jest znacznie mniej oczywista.Jest
words
null
? Jestwords.get(0) null
? Obie ? Dlaczego jedno, drugie, a może oba sąnull
? Czy to błąd kodowania wDictionary
(wywoływana metoda konstruktora?)? Czy to błąd kodowaniaLookupService
? (konstruktor? wywołana metoda?)?Wreszcie będziemy musieli sprawdzić więcej kodu, aby znaleźć źródło błędu, aw bardziej złożonej klasie może nawet użyć debuggera, aby łatwiej zrozumieć, co się stało.
Ale dlaczego prosta rzecz (brak kontroli zerowej) staje się złożonym problemem?
Ponieważ pozwoliliśmy na identyfikację początkowego błędu / braku określonego przecieku na niższych komponentach.
Wyobraź sobie, że
LookupService
nie była to usługa lokalna, ale usługa zdalna lub biblioteka strony trzeciej z kilkoma informacjami debugowania lub wyobraź sobie, że nie masz 2 warstw, ale 4 lub 5 warstw wywołań obiektów przednull
wykryciem? Problem byłby jeszcze bardziej złożony do analizy.Sposobem na faworyzowanie jest:
W ten sposób nie ma bólu głowy: wyjątek zostaje zgłoszony natychmiast po otrzymaniu:
Zauważ, że tutaj zilustrowałem problem z konstruktorem, ale wywołanie metody może mieć takie samo ograniczenie kontroli innej niż null.
źródło
Na marginesie, to szybko się nie udawało, zanim
Object#requireNotNull
zostało wdrożone nieco inaczej przed java-9 w niektórych klasach jre. Załóżmy, że przypadek:W java-8 kompiluje się jako (tylko odpowiednie części)
Zasadniczo operacja
yourReference.getClass
taka jak: - która zakończy się niepowodzeniem, jeśli twoja wartość referencyjna tonull
.W jdk-9 wszystko się zmieniło, gdzie kompiluje się ten sam kod, co
Lub w zasadzie
Objects.requireNotNull (yourReference)
źródło
Podstawowym zastosowaniem jest
NullPointerException
natychmiastowe sprawdzanie i rzucanie .Lepszą alternatywą (skrótem) do spełnienia tego samego wymagania jest adnotacja @NonNull autorstwa lombok.
źródło
Wyjątek wskaźnika zerowego jest zgłaszany, gdy uzyskujesz dostęp do elementu obiektu, który znajduje się
null
w późniejszym momencie.Objects.requireNonNull()
natychmiast sprawdza wartość i natychmiast rzuca wyjątek, nie poruszając się do przodu.źródło
Myślę, że powinien być używany w konstruktorach kopiowania i niektórych innych przypadkach, takich jak DI, których parametr wejściowy jest obiektem, powinieneś sprawdzić, czy parametr jest pusty. W takich okolicznościach można wygodnie korzystać z tej metody statycznej.
źródło
W kontekście rozszerzeń kompilatora, które implementują sprawdzanie Nullability (np .: uber / NullAway ),
Objects.requireNonNull
należy używać go nieco oszczędnie w sytuacjach, gdy masz pole zerowe, o którym wiesz, że w pewnym momencie kodu nie ma wartości null.W ten sposób istnieją dwa główne zastosowania:
Uprawomocnienie
Oznaczenie wartości zerowej (zmiana z @Nullable na @Nonnull)
Przykładowe użycie oznakowania dopuszczającego wartość zerową:
źródło
Oprócz wszystkich poprawnych odpowiedzi:
Używamy go w strumieniach reaktywnych. Zazwyczaj powstałe
NullpointerException
s są zawijane w inne wyjątki w zależności od ich wystąpienia w strumieniu. Dlatego możemy później łatwo zdecydować, jak obsłużyć błąd.Tylko przykład: Wyobraź sobie, że masz
gdzie
parseAndValidate
WrappsNullPointerException
odrequireNonNull
w sposóbParsingException
.Teraz możesz zdecydować, np. Kiedy spróbować ponownie, czy nie:
Bez tej kontroli w metodzie wystąpi wyjątek NullPointerException
save
, co spowoduje niekończące się próby. Warto nawet wyobrazić sobie długą subskrypcję, dodającTeraz
RetryExhaustException
zabije twoją subskrypcję.źródło