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łaniu null
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ły null
.
Załóżmy, że Dictionary
klasa, która komponuje LookupService
i List
od String
reprezentujący słów zawartych w. Pola te są zaprojektowane, aby nie być null
, a jednym z nich jest przekazywana w Dictionary
konstruktorze.
Załóżmy teraz, że „zła” implementacja Dictionary
bez null
sprawdzania we wpisie metody (tutaj jest to konstruktor):
public class Dictionary {
private final List<String> words;
private final LookupService lookupService;
public Dictionary(List<String> words) {
this.words = this.words;
this.lookupService = new LookupService(words);
}
public boolean isFirstElement(String userData) {
return lookupService.isFirstElement(userData);
}
}
public class LookupService {
List<String> words;
public LookupService(List<String> words) {
this.words = words;
}
public boolean isFirstElement(String userData) {
return words.get(0).contains(userData);
}
}
Teraz wywołajmy Dictionary
konstruktor z null
referencją do words
parametru:
Dictionary dictionary = new Dictionary(null);
// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
JVM rzuca NPE na tę instrukcję:
return words.get(0).contains(userData);
Wyjątek w wątku „main” java.lang.NullPointerException
w LookupService.isFirstElement (LookupService.java:5)
at Dictionary.isFirstElement (Dictionary.java:15)
at Dictionary.main (Dictionary.java:22)
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
? Jest words.get(0) null
? Obie ? Dlaczego jedno, drugie, a może oba są null
? Czy to błąd kodowania w Dictionary
(wywoływana metoda konstruktora?)? Czy to błąd kodowania LookupService
? (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 przed null
wykryciem? Problem byłby jeszcze bardziej złożony do analizy.
Sposobem na faworyzowanie jest:
public Dictionary(List<String> words) {
this.words = Objects.requireNonNull(words);
this.lookupService = new LookupService(words);
}
W ten sposób nie ma bólu głowy: wyjątek zostaje zgłoszony natychmiast po otrzymaniu:
// exception thrown early : in the constructor
Dictionary dictionary = new Dictionary(null);
// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Wyjątek w wątku „main” java.lang.NullPointerException
at java.util.Objects.requireNonNull (Objects.java:203)
w com.Dictionary. (Dictionary.java:15)
at com.Dictionary.main (Dictionary.java:24)
Zauważ, że tutaj zilustrowałem problem z konstruktorem, ale wywołanie metody może mieć takie samo ograniczenie kontroli innej niż null.