Unikanie! = Instrukcje null

4014

Używam object != nulldużo, aby tego uniknąć NullPointerException.

Czy jest na to dobra alternatywa?

Na przykład często używam:

if (someobject != null) {
    someobject.doCalc();
}

To sprawdza czy NullPointerExceptiondla someobjectobiektu w powyższym fragmencie.

Należy pamiętać, że zaakceptowana odpowiedź może być nieaktualna, najnowsze https://stackoverflow.com/a/2386013/12943 .

Goran Martinic
źródło
120
@Shervin Zachęcanie wartości null sprawia, że ​​kod jest mniej zrozumiały i mniej niezawodny.
Tom Hawtin - tackline
44
Zaproponowano operatory Elvisa, ale wygląda na to, że nie będzie w Javie 7 . Szkoda,? ?: i? [] są niesamowicie oszczędzającymi czas.
Scott,
72
Nieużywanie wartości null jest lepsze niż większość innych sugestii tutaj. Zgłaszaj wyjątki, nie zwracaj ani nie dopuszczaj wartości null. BTW - słowo kluczowe „asert” jest bezużyteczne, ponieważ jest domyślnie wyłączone. Użyj zawsze włączonego mechanizmu awarii
ianpojman
13
To jeden z powodów, dla których używam teraz Scali. W Scali wszystko nie jest zerowalne. Jeśli chcesz zezwolić na przekazanie lub zwrócenie „niczego”, musisz dokładnie użyć Opcji [T] zamiast tylko argumentu Tals lub typu zwrotu.
Thekwasti
11
@thoft Rzeczywiście, niesamowity Optiontyp Scali jest zniekształcony niechęcią języka do kontrolowania lub odrzucania null. Wspomniałem o tym na hackernews i przegłosowałem i powiedziałem, że „nikt nie używa null w Scali”. Zgadnij co, już znajduję wartości zerowe w napisanej przez współpracownika Scali. Tak, „robią to źle” i trzeba o tym wiedzieć, ale faktem jest, że system typów językowych powinien mnie przed tym chronić, a tak nie jest :(
Andres F.

Odpowiedzi:

2642

Wydaje mi się, że to dość powszechny problem, z którym w pewnym momencie spotykają się programiści od młodszych do średniozaawansowanych: albo nie znają kontraktów, w których uczestniczą, ani nie ufają im, i defensywnie sprawdzają zerowe wartości. Dodatkowo, podczas pisania własnego kodu, zwykle polegają na zwracaniu wartości null w celu wskazania czegoś, co wymaga od dzwoniącego sprawdzenia wartości null.

Innymi słowy, istnieją dwa przypadki, w których pojawia się sprawdzanie wartości NULL:

  1. Gdzie null jest prawidłową odpowiedzią w odniesieniu do umowy; i

  2. Gdzie nie jest to prawidłowa odpowiedź.

(2) jest łatwe. Użyj assertinstrukcji (twierdzeń) lub zezwól na niepowodzenie (na przykład NullPointerException ). Asercje to wysoce niewykorzystana funkcja Java, która została dodana w wersji 1.4. Składnia jest następująca:

assert <condition>

lub

assert <condition> : <object>

gdzie <condition>jest wyrażeniem logicznym i <object>jest obiektem, którego wynik toString()metody zostanie uwzględniony w błędzie.

assertOświadczenie wyrzuca Error( AssertionError) jeśli warunek nie jest prawdą. Domyślnie Java ignoruje twierdzenia. Można włączyć asercje, przekazując opcję -eado JVM. Możesz włączać i wyłączać asercje dla poszczególnych klas i pakietów. Oznacza to, że możesz sprawdzać poprawność kodu z asercjami podczas opracowywania i testowania oraz wyłączać je w środowisku produkcyjnym, chociaż moje testy wykazały prawie żaden wpływ na wydajność z asercji.

Nieużywanie asercji w tym przypadku jest OK, ponieważ kod po prostu się nie powiedzie, co stanie się, jeśli użyjesz asercji. Jedyną różnicą jest to, że w przypadku stwierdzeń może się to zdarzyć wcześniej, w bardziej znaczący sposób i być może z dodatkowymi informacjami, które mogą pomóc ci dowiedzieć się, dlaczego tak się stało, jeśli się tego nie spodziewałeś.

(1) jest trochę trudniejszy. Jeśli nie masz kontroli nad kodem, który dzwonisz, utkniesz. Jeśli null jest prawidłową odpowiedzią, musisz ją sprawdzić.

Jeśli jednak kontrolujesz kod (i często tak się dzieje), to inna historia. Unikaj używania wartości null jako odpowiedzi. Dzięki metodom, które zwracają kolekcje, jest to łatwe: zwracaj puste kolekcje (lub tablice) zamiast prawie zer.

W przypadku braku kolekcji może być trudniej. Rozważ to jako przykład: jeśli masz te interfejsy:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

gdzie Parser pobiera dane wejściowe od użytkownika i znajduje coś do zrobienia, być może, jeśli implementujesz interfejs wiersza poleceń. Teraz możesz sprawić, że umowa zwróci wartość zerową, jeśli nie zostaną podjęte odpowiednie działania. To prowadzi do sprawdzenia zerowego, o którym mówisz.

Alternatywnym rozwiązaniem jest nigdy nie zwracać wartości null, a zamiast tego używać wzorca Null Object :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Porównać:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

do

ParserFactory.getParser().findAction(someInput).doSomething();

co jest znacznie lepszym projektem, ponieważ prowadzi do bardziej zwięzłego kodu.

To powiedziawszy, być może jest całkowicie właściwe, aby metoda findAction () zgłosiła wyjątek z sensownym komunikatem o błędzie - szczególnie w tym przypadku, gdy polegasz na danych wejściowych użytkownika. O wiele lepiej byłoby, gdyby metoda findAction zgłosiła wyjątek, niż metoda wywołująca wysadzenie prostego wyjątku NullPointerException bez wyjaśnienia.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

Lub jeśli uważasz, że mechanizm try / catch jest zbyt brzydki, zamiast nic nie rób, domyślna akcja powinna dostarczyć użytkownikowi informacji zwrotnej.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}
cletus
źródło
631
Nie zgadzam się z twoimi stwierdzeniami dotyczącymi działania DO_NOTHING. Jeśli metoda find action nie może znaleźć akcji, wówczas należy zwrócić wartość null. „Znalazłeś” akcję w kodzie, która tak naprawdę nie została znaleziona, co narusza zasadę metody, aby znaleźć użyteczną akcję.
MetroidFan2002
266
Zgadzam się, że null jest nadużywany w Javie, szczególnie w przypadku list. Tak wiele apis byłoby lepiej, gdyby zwracały pustą listę / tablicę / kolekcję zamiast null. Wiele razy null jest używany, gdy zamiast niego powinien zostać zgłoszony wyjątek. Wyjątek powinien zostać zgłoszony, jeśli parser nie może parsować.
Laplie Anderson
92
Ten ostatni przykład to IIRC, wzorzec projektowy Null Object.
Steven Evers,
62
@Cshah (i MetroidFan2002). Proste, umieść to w umowie, a wtedy jasne będzie, że nieodnaleziona akcja nic nie zrobi. Jeśli jest to ważna informacja dla osoby dzwoniącej, podaj sposób, aby odkryć, że była to akcja nieodnaleziona (tj. Zapewnij metodę sprawdzania, czy wynikiem był obiekt DO_NOTHING). Alternatywnie, jeśli normalnie można znaleźć akcję, to nadal nie powinieneś zwracać wartości null, ale zamiast tego rzucić wyjątek konkretnie wskazujący ten warunek - nadal skutkuje to lepszym kodem. W razie potrzeby podaj osobną metodę zwracającą wartość logiczną, aby sprawdzić, czy istnieje akcja.
Kevin Brock
35
Zwięzły nie równa się kodowi jakości. Przepraszam, że tak uważasz. Twój kod ukrywa sytuacje, w których błąd byłby korzystny.
gshauger
634

Jeśli używasz (lub planujesz użyć) środowiska Java IDE, takiego jak JetBrains IntelliJ IDEA , Eclipse lub Netbeans, lub narzędzia takiego jak findbugs, możesz użyć adnotacji, aby rozwiązać ten problem.

Zasadniczo masz @Nullablei @NotNull.

Możesz użyć metody i parametrów, takich jak to:

@NotNull public static String helloWorld() {
    return "Hello World";
}

lub

@Nullable public static String helloWorld() {
    return "Hello World";
}

Drugi przykład nie zostanie skompilowany (w IntelliJ IDEA).

Gdy użyjesz pierwszej helloWorld()funkcji w innym fragmencie kodu:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

Teraz kompilator IntelliJ IDEA powie ci, że sprawdzenie jest bezużyteczne, ponieważ helloWorld()funkcja nigdy nie powróci null.

Za pomocą parametru

void someMethod(@NotNull someParameter) { }

jeśli napiszesz coś takiego:

someMethod(null);

To się nie skompiluje.

Ostatni przykład użycia @Nullable

@Nullable iWantToDestroyEverything() { return null; }

Robiąc to

iWantToDestroyEverything().something();

I możesz być pewien, że tak się nie stanie. :)

To dobry sposób, aby pozwolić kompilatorowi sprawdzić coś więcej niż zwykle i wymusić silniejsze kontrakty. Niestety nie jest obsługiwany przez wszystkie kompilatory.

W IntelliJ IDEA 10.5 i nowszych dodano obsługę wszystkich innych @Nullable @NotNullimplementacji.

Zobacz wpis na blogu Bardziej elastyczne i konfigurowalne adnotacje @ Nullable / @ NotNull .

Luca Molteni
źródło
119
@NotNull, @Nullablea inne adnotacje o nieważności są częścią JSR 305 . Możesz ich również użyć do wykrycia potencjalnych problemów z narzędziami takimi jak FindBugs .
Jacek S,
32
Dziwnie denerwuje mnie to, że interfejsy @NotNulli znajdują się @Nullablew pakiecie com.sun.istack.internal. (Chyba kojarzę com.sun z ostrzeżeniami o używaniu zastrzeżonego API).
Jonik
20
przenośność kodu jest zerowa w przypadku jetbrain. Pomyślałbym dwa razy (kwadrat) przed obniżeniem do poziomu ide. Podobnie jak Jacek S powiedział, że są częścią JSR w jakikolwiek sposób, który moim zdaniem był JSR303.
Java Ka Baby,
10
Naprawdę nie sądzę, aby użycie niestandardowego kompilatora było realnym rozwiązaniem tego problemu.
Shivan Dragon,
64
Dobrą rzeczą adnotacji, które @NotNulli @Nullablesą to, że ładnie pogorszyć, gdy kod źródłowy jest zbudowany przez system, który ich nie rozumieją. W rezultacie argument, że kod nie jest przenośny, może być nieprawidłowy - jeśli używasz systemu, który obsługuje i rozumie te adnotacje, zyskujesz dodatkową korzyść z bardziej rygorystycznego sprawdzania błędów, w przeciwnym razie dostajesz mniej, ale kod nadal powinien zbuduj dobrze, a jakość twojego programu działa TAKIE SAMO, ponieważ te adnotacje i tak nie zostały wymuszone w czasie wykonywania. Poza tym wszystkie kompilatory są niestandardowe ;-)
am
321

Jeśli wartości zerowe są niedozwolone

Jeśli twoja metoda jest wywoływana zewnętrznie, zacznij od czegoś takiego:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Następnie, w pozostałej części tej metody, będziesz wiedział, że objectnie jest zerowa.

Jeśli jest to metoda wewnętrzna (nie jest częścią interfejsu API), po prostu udokumentuj, że nie może być pusta i to wszystko.

Przykład:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

Jeśli jednak twoja metoda przekazuje wartość, a następna metoda przekazuje ją itd., Może to powodować problemy. W takim przypadku możesz sprawdzić argument jak wyżej.

Jeśli dozwolone jest zero

To naprawdę zależy. Jeśli okaże się, że często robię coś takiego:

if (object == null) {
  // something
} else {
  // something else
}

Więc rozgałęziam się i robię dwie zupełnie różne rzeczy. Nie ma brzydkiego fragmentu kodu, ponieważ naprawdę muszę zrobić dwie różne rzeczy w zależności od danych. Na przykład, czy powinienem pracować na danych wejściowych, czy powinienem obliczyć dobrą wartość domyślną?


W rzeczywistości rzadko używam idiomu „ if (object != null && ...”.

Podanie przykładów może być łatwiejsze, jeśli pokażesz przykłady tego, gdzie zazwyczaj używasz tego idiomu.

myplacedk
źródło
94
Po co rzucać IllegalArgumentException? Myślę, że wyjątek NullPointerException byłby bardziej przejrzysty, a także zostałby zgłoszony, jeśli sam nie wykonasz kontroli zerowej. Użyłbym albo twierdzić, albo w ogóle nic.
Axel,
23
Jest mało prawdopodobne, aby każda inna wartość niż zero była akceptowalna. Możesz mieć IllegalArgumentException, OutOfRageException itp. Czasami ma to sens. Innym razem tworzysz wiele klas wyjątków, które nie dodają żadnej wartości, po prostu używasz IllegalArgumentException. Nie ma sensu mieć jednego wyjątku dla wejścia zerowego, a drugiego dla wszystkiego innego.
myplacedk
7
Tak, zgadzam się na zasadę szybkiego działania, ale w powyższym przykładzie wartość nie jest przekazywana, ale jest obiektem, na którym zostanie wywołana metoda. Więc nie działa równie szybko, a dodanie czeku zerowego, aby rzucić wyjątek, który i tak zostałby rzucony w tym samym czasie i miejscu, wydaje się, że debugowanie nie jest łatwiejsze.
Axel
8
Dziura bezpieczeństwa? JDK jest pełen takiego kodu. Jeśli nie chcesz, aby użytkownik widział ślady stosu, po prostu je wyłącz. Nikt nie sugerował, że zachowanie nie jest udokumentowane. MySQL jest napisany w języku C, w którym dereferencje zerowych wskaźników są nieokreślonym zachowaniem, niczym w rzucaniu wyjątku.
fgb
8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen
238

Wow, prawie nienawidzę dodawać kolejnej odpowiedzi, gdy mamy 57 różnych sposobów na zarekomendowanie NullObject pattern, ale myślę, że niektórzy ludzie zainteresowani tym pytaniem mogą wiedzieć, że na stole jest propozycja dodania Java 7 „zerowo bezpieczna” handling ” - usprawniona składnia dla logiki jeśli nie jest równa zeru ”.

Przykład podany przez Alexa Millera wygląda następująco:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

Te ?.środki tylko de referencyjne lewy identyfikator, jeśli nie jest null, inaczej ocenić pozostałą część wyrazu jak null. Niektórzy ludzie, jak członek Javy Posse, Dick Wall i wyborcy w Devoxx, naprawdę uwielbiają tę propozycję, ale jest też sprzeciw, ponieważ faktycznie zachęca do większego wykorzystania nulljako wartownika.


Aktualizacja: oficjalna propozycja dla null-safe operatora w Java 7 został złożony w ramach Projektu Coin. Składnia jest nieco inna niż w powyższym przykładzie, ale to samo pojęcie.


Aktualizacja: Oferta operatora zerowo bezpieczna nie trafiła do monety projektu. Tak więc nie zobaczysz tej składni w Javie 7.

erickson
źródło
20
Myślę, że to źle. Powinien istnieć sposób na określenie, że dana zmienna ZAWSZE ma wartość inną niż null.
Thorbjørn Ravn Andersen
5
Aktualizacja: wniosek nie utworzy Java7. Zobacz blogs.sun.com/darcy/entry/project_coin_final_five .
Boris Terzic
9
Ciekawy pomysł, ale wybór składni jest absurdalny; Nie chcę, aby baza kodów pełna znaków zapytania była przypięta do każdego połączenia.
Rob
7
Ten operator istnieje w Groovy , więc ci, którzy chcą go używać, nadal mają tę opcję.
Muhd
8
To najbardziej pomysłowy pomysł, jaki widziałem. Należy go dodać do każdego rozsądnego języka składni C. Wolę wszędzie „przypinać znaki zapytania” niż przewijać ekrany linii lub unikać „klauzul ochronnych” przez cały dzień.
Victor
196

Jeśli niezdefiniowane wartości nie są dozwolone:

Możesz skonfigurować swoje IDE, aby ostrzegało przed potencjalnym zerowym odwołaniem. Np. W Eclipse, zobacz Preferencje> Java> Kompilator> Błędy / Ostrzeżenia / Analiza zerowa .

Jeśli dozwolone są niezdefiniowane wartości:

Jeśli chcesz zdefiniować nowy interfejs API, w którym sens mają niezdefiniowane wartości , użyj Wzorca Opcji (może być znany z języków funkcjonalnych). Ma następujące zalety:

  • W interfejsie API jest wyraźnie określone, czy dane wejściowe lub wyjściowe istnieją, czy nie.
  • Kompilator zmusza cię do obsługi „niezdefiniowanej” sprawy.
  • Opcja jest monadą , więc nie ma potrzeby pełnego sprawdzania wartości zerowej, wystarczy użyć map / foreach / getOrElse lub podobnego kombinatora, aby bezpiecznie użyć wartości (przykład) .

Java 8 ma wbudowaną Optionalklasę (zalecane); dla wcześniejszych wersji, istnieją alternatywy biblioteki, na przykład Guava „s Optionallub FunctionalJava ” s Option. Ale podobnie jak wiele funkcjonalnych wzorców, użycie Option w Javie (nawet 8) daje całkiem spory przykład, który można zredukować, używając mniej szczegółowego języka JVM, np. Scala lub Xtend.

Jeśli masz do czynienia z interfejsem API, który może zwracać wartości zerowe , nie możesz wiele zrobić w Javie. Xtend i Groovy mają operator Elvisa ?: i operator wyzerowania bezpieczny dla wartości zerowych?. , ale należy pamiętać, że to zwraca wartość zerową w przypadku odwołania zerowego, więc po prostu „odracza” prawidłowe zarządzanie wartością zerową.

thSoft
źródło
22
Rzeczywiście, wzór Opcji jest niesamowity. Istnieją pewne odpowiedniki Java. Guava zawiera ograniczoną wersję tego, zwanego Opcjonalnym, który pomija większość funkcjonalnych rzeczy. W Haskell ten wzór nazywa się może.
Ben Hardy,
9
Klasa fakultatywna będzie dostępna w Javie 8
Pierre Henry,
1
... i nie ma (jeszcze) mapy ani płaskiej mapy: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft 25.04.13
3
Opcjonalny wzór niczego nie rozwiązuje; zamiast jednego potencjalnie zerowego obiektu, teraz masz dwa.
Boann
1
@Boann, jeśli zastosujesz go ostrożnie, rozwiążesz wszystkie problemy NPE. Jeśli nie, to myślę, że występuje problem z „użytkowaniem”.
Louis F.
189

Tylko w tej sytuacji -

Nie sprawdzanie, czy zmienna ma wartość null przed wywołaniem metody równości (przykład porównania ciągów poniżej):

if ( foo.equals("bar") ) {
 // ...
}

spowoduje, że NullPointerExceptionif foonie istnieje.

Możesz tego uniknąć, porównując swoje w Stringten sposób:

if ( "bar".equals(foo) ) {
 // ...
}
echoks
źródło
42
Zgadzam się - tylko w tej sytuacji. Nie mogę znieść programistów, którzy przenieśli to na kolejny niepotrzebny poziom i piszą, jeśli (null! = MyVar) ... wygląda mi po prostu brzydko i nie ma sensu!
Alex Worden
20
Jest to szczególny przykład, prawdopodobnie najczęściej stosowany, ogólnej dobrej praktyki: jeśli ją znasz, zawsze rób <object that you know that is not null>.equals(<object that might be null>);. Działa dla innych metod niż te equals, które znasz z kontraktu i te metody mogą obsługiwać nullparametry.
Stef
9
To pierwszy przykład, jaki widziałem w Yoda Warunki, który ma sens
Erin Drummond
6
NullPointerExceptions są zgłaszane z jakiegoś powodu. Są rzucane, ponieważ obiekt jest pusty w miejscu, w którym nie powinien być. Zadaniem programistów jest USUNIĘCIE tego, a nie UKRYJ problem.
Oliver Watkins
1
Try-Catch-DoNothing ukrywa problem, jest to ważna praktyka do obejścia brakującego cukru w ​​języku.
echox
167

Wraz z Javą 8 pojawia się nowa java.util.Optionalklasa, która prawdopodobnie rozwiązuje część problemu. Można przynajmniej powiedzieć, że poprawia to czytelność kodu, aw przypadku publicznych interfejsów API uczynić umowę API bardziej przejrzystą dla programisty klienta.

Działają w ten sposób:

Obiekt opcjonalny dla danego typu ( Fruit) jest tworzony jako typ zwracany przez metodę. Może być pusty lub zawierać Fruitobiekt:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Teraz spójrz na ten kod, w którym przeszukujemy listę Fruit( fruits) dla danej instancji Fruit:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Za pomocą map()operatora można wykonać obliczenia na - lub wyodrębnić wartość - z opcjonalnego obiektu. orElse()pozwala zapewnić rezerwę na brakujące wartości.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

Oczywiście sprawdzenie wartości pustej / pustej jest nadal konieczne, ale przynajmniej deweloper ma świadomość, że wartość może być pusta, a ryzyko zapomnienia o sprawdzeniu jest ograniczone.

W interfejsie API zbudowanym od podstaw za pomocą Optionalilekroć zwracana wartość może być pusta, a zwracanie zwykłego obiektu tylko wtedy, gdy nie może być null(konwencja), kod klienta może porzucić sprawdzanie wartości zerowej prostych wartości zwracanych przez prosty obiekt ...

Oczywiście Optionalmoże być również użyty jako argument metody, być może lepszy sposób wskazywania opcjonalnych argumentów niż 5 lub 10 metod przeciążania w niektórych przypadkach.

Optionaloferuje inne wygodne metody, takie jak takie, orElsektóre pozwalają na użycie wartości domyślnej i ifPresentdziała z wyrażeniami lambda .

Zapraszam do lektury tego artykułu (mojego głównego źródła do napisania tej odpowiedzi), w którym NullPointerExceptionproblematyczne (i ogólnie zerowy wskaźnik), a także (częściowe) rozwiązanie, które Optionalzostały przez nie wyjaśnione: Java Optional Objects .

Pierre Henry
źródło
11
Guawa Google ma opcjonalną implementację dla Java 6+.
Bradley Gottfried
13
To bardzo ważne, aby podkreślić, że za pomocą opcjonalnej tylko z ifPresent () czy nie dodać wiele wartości powyżej normalnej kontroli zerowej. Jego podstawową wartością jest to, że jest to monada, którą można wykorzystać w łańcuchach funkcyjnych mapy / flapMap, która osiąga wyniki podobne do operatora Elvisa w Groovy wspomnianego gdzie indziej. Jednak nawet bez tego użycia uważam, że składnia orElse / orElseThrow jest również bardzo przydatna.
Cornel Masson
Ten blog ma dobry wpis na Opcjonalnie winterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC
4
Dlaczego ludzie mają taką tendencję if(optional.isPresent()){ optional.get(); }zamiastoptional.ifPresent(o -> { ...})
Satyendra Kumar
1
Tak więc, poza wskazówkami umownymi dotyczącymi API, tak naprawdę chodzi tylko o to, aby zaspokoić potrzeby funkcjonalnych programistów, którzy bez końca lubią metody łączenia.
zmiażdżyć
125

W zależności od rodzaju obiektów, które sprawdzasz, możesz użyć niektórych klas w apache commons, takich jak: apache commons lang i apache commons kolekcje

Przykład:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

lub (w zależności od tego, co musisz sprawdzić):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

Klasa StringUtils jest tylko jedną z wielu; w społecznościach jest całkiem sporo dobrych klas, które wykonują zerową bezpieczną manipulację.

Poniżej znajduje się przykład użycia walidacji zerowej w JAVA po dołączeniu biblioteki apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

A jeśli używasz Springa, Spring ma również tę samą funkcjonalność w swoim pakiecie, zobacz bibliotekę (spring-2.4.6.jar)

Przykład użycia tej statycznej klasyf od wiosny (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
javamonkey79
źródło
5
Możesz także użyć bardziej ogólnej wersji z Apache Commons, całkiem przydatnej na początku metod sprawdzania parametrów, które znajduję. Validate.notNull (obiekt, „obiekt nie może mieć wartości null”); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny
@monojohnny sprawdza poprawność użycia instrukcji Aser w ?. pytam o to, ponieważ Assert może być aktywowany / dezaktywowany w JVM i sugeruje, aby nie używać w produkcji.
Kurapika,
Nie myśl tak - uważam, że po prostu zgłasza wyjątek RuntimeException, jeśli sprawdzanie poprawności się nie powiedzie
monojohnny
96
  • Jeśli uważasz, że obiekt nie powinien mieć wartości NULL (lub jest to błąd), użyj potwierdzenia.
  • Jeśli twoja metoda nie akceptuje parametrów zerowych, powiedz to w javadoc i użyj asert.

Musisz sprawdzić obiekt! = Null tylko, jeśli chcesz obsłużyć przypadek, w którym obiekt może być null ...

Istnieje propozycja dodania nowych adnotacji w Javie 7, aby pomóc z parametrami null / notnull: http://tech.puredanger.com/java7/#jsr308

pgras
źródło
91

Jestem fanem kodu „fail fast”. Zadaj sobie pytanie - czy robisz coś użytecznego w przypadku, gdy parametr ma wartość null? Jeśli nie masz jednoznacznej odpowiedzi na pytanie, co powinien zrobić Twój kod w tym przypadku ... To znaczy, nigdy nie powinno być zerowe, to zignoruj ​​go i zezwól na zgłoszenie wyjątku NullPointerException. Kod wywołujący będzie miał takie samo znaczenie dla NPE, jak dla IllegalArgumentException, ale deweloperowi łatwiej będzie debugować i zrozumieć, co poszło nie tak, jeśli zostanie wygenerowany NPE, niż twój kod próbujący wykonać inną nieoczekiwaną nieprzewidzianą sytuację. logika - która ostatecznie powoduje awarię aplikacji.

Alex Worden
źródło
2
lepiej używać asercji, tj. Contract.notNull (abc, „abc musi być różny od zera, czy nie załadowało się podczas xyz?”); - jest to bardziej kompaktowy sposób niż robienie if (abc! = null) {wyrzuć nowy wyjątek RuntimeException ...}
ianpojman
76

Ramy kolekcji Google to dobry i elegancki sposób na uzyskanie kontroli zerowej.

W klasie biblioteki jest taka metoda:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

A użycie jest (z import static):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

Lub w twoim przykładzie:

checkNotNull(someobject).doCalc();
2427
źródło
71
mmm, jaka jest różnica? p.getAge () rzuciłby ten sam NPE z mniejszym narzutem i wyraźniejszym śladem stosu. czego mi brakuje?
mysomic
15
Lepiej jest wyrzucić IllegalArgumentException („e == null”) w swoim przykładzie, ponieważ wyraźnie wskazuje, że jest to wyjątek przeznaczony dla programistów (wraz z wystarczającą ilością informacji, aby opiekun mógł zidentyfikować problem). Wyjątki NullPointerException powinny być zarezerwowane dla JVM, ponieważ wówczas wyraźnie wskazuje, że było to niezamierzone (i zwykle zdarza się gdzieś trudno je zidentyfikować)
Thorbjørn Ravn Andersen
6
To jest teraz częścią Google Guava.
Steven Benitez
32
Pachnie dla mnie nadmierną inżynierią. Po prostu pozwól JVM rzucić NPE i nie zaśmiecaj swojego kodu tymi śmieciami.
Alex Worden,
5
Podoba mi się i otwieram większość metod i konstruktorów z wyraźną kontrolą argumentów; jeśli wystąpi błąd, metody zawsze zawodzą w pierwszych kilku wierszach i znam obraźliwe odniesienie, nie znajdując czegoś takiegogetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall
75

Czasami masz metody, które działają na jego parametrach, które definiują operację symetryczną:

a.f(b); <-> b.f(a);

Jeśli wiesz, że b nigdy nie może być zerowy, możesz go po prostu zamienić. Jest to najbardziej przydatne dla równych: Zamiast foo.equals("bar");lepiej zrobić "bar".equals(foo);.

Johannes Schaub - litb
źródło
2
Ale musisz założyć equals(może to być dowolna metoda), że poprawnie obsłuży wartość null. Naprawdę to wszystko polega na przekazaniu odpowiedzialności komuś innemu (lub innej metodzie).
Supericy
4
@Supericy Zasadniczo tak, ale equals(lub jakakolwiek inna metoda) i tak musi to sprawdzić null. Lub wyraźnie zaznacz, że tak nie jest.
Angelo Fuchs
74

Zamiast Wzorca Null Object - który ma swoje zastosowanie - można rozważyć sytuacje, w których obiekt NULL jest błędem.

Po zgłoszeniu wyjątku sprawdź ślad stosu i przeanalizuj błąd.

Jim Nelson
źródło
17
Problem polega na tym, że zazwyczaj tracisz kontekst, ponieważ wyjątek NullPointerException nie wskazuje, KTÓRA zmienna ma wartość null, i możesz mieć kilka operacji „.” Na linii. Użycie „if (foo == null) wrzuć nowy wyjątek RuntimeException („ foo == null ”)” pozwala ci jednoznacznie stwierdzić, co jest nie tak, dając śledzeniu stosu znacznie więcej wartości tym, którzy muszą to naprawić.
Thorbjørn Ravn Andersen
1
W przypadku Andersena - chciałbym, aby system wyjątków Javy mógł zawierać nazwę zmiennej, nad którą pracujemy, aby NullPointerExceptions nie tylko wskazywał linię, w której wystąpił wyjątek, ale także nazwę zmiennej. Powinno to działać dobrze w nieobrobionym oprogramowaniu.
fwielstra
Nie udało się uzyskać jego pracy jeszcze, ale to ma dokładnie rozwiązać ten problem.
MatrixFrog,
Jaki jest problem? Wystarczy złapać NPE na odpowiednim poziomie, gdzie jest wystarczająca ilość kontekstu, zrzucić informacje o kontekście i ponownie wyrzucić wyjątek ... z Javą jest to takie proste.
user1050755,
3
Miałem profesora, który głosił przeciwko łańcuchowi wywołań metod. Jego teoria była taka, że ​​powinieneś uważać na łańcuchy połączeń, które były dłuższe niż 2 metody. Nie wiem, czy to trudna reguła, ale zdecydowanie usuwa większość problemów ze śladami stosu NPE.
RustyTheBoyRobot 16.04.13
74

Null nie jest „problemem”. Jest integralną częścią kompletnego zestawu narzędzi do modelowania. Oprogramowanie ma na celu modelowanie złożoności świata, a ciężar zerowy ponosi. Null oznacza „Brak danych” lub „Nieznany” w Javie i tym podobne. Dlatego do tych celów należy używać wartości zerowych. Nie preferuję wzorca „obiekt zerowy”; Myślę, że rodzi to problem „ kto będzie pilnował strażników ”.
Jeśli zapytasz mnie, jak ma na imię moja dziewczyna, powiem ci, że nie mam dziewczyny. W języku Java zwracam null. Alternatywą byłoby rzucenie znaczącego wyjątku, aby wskazać jakiś problem, który nie może być (lub nie „

  1. W przypadku „nieznanego pytania” podaj „nieznaną odpowiedź”. (Bądź zerowy, jeśli jest to poprawne z biznesowego punktu widzenia) Sprawdzanie argumentów dla wartości null raz w metodzie przed użyciem zwalnia wielu dzwoniących od sprawdzania ich przed wywołaniem.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }

    Poprzedni prowadzi do normalnego przepływu logiki, aby nie uzyskać zdjęcia nieistniejącej dziewczyny z mojej biblioteki zdjęć.

    getPhotoOfThePerson(me.getGirlfriend())

    I pasuje do nowego nadchodzącego Java API (czekamy)

    getPhotoByName(me.getGirlfriend()?.getName())

    Chociaż raczej „normalnym przepływem biznesu” jest nie znajdowanie zdjęć przechowywanych w bazie danych dla niektórych osób, w innych przypadkach używałem par takich jak poniżej.

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);

    I nie bój się pisać <alt> + <shift> + <j>(generuj javadoc w Eclipse) i pisz trzy dodatkowe słowa dla swojego publicznego API. Będzie to więcej niż wystarczające dla wszystkich oprócz tych, którzy nie czytają dokumentacji.

    /**
     * @return photo or null
     */

    lub

    /**
     * @return photo, never null
     */
  2. Jest to raczej teoretyczny przypadek iw większości przypadków powinieneś preferować java null safe API (w przypadku, gdy zostanie wydany za 10 lat), ale NullPointerExceptionjest podklasą Exception. Jest to więc forma Throwablewskazująca warunki, które rozsądna aplikacja może chcieć złapać ( javadoc )! Aby skorzystać z pierwszej największej zalety wyjątków i oddzielić kod obsługi błędów od kodu „zwykłego” ( według twórców Javy ), jak dla mnie, należy łapać NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }

    Mogą pojawić się pytania:

    P: Co się stanie, jeśli getPhotoDataSource()zwróci wartość null?
    A. To zależy od logiki biznesowej. Jeśli nie uda mi się znaleźć albumu ze zdjęciami, nie pokażę ci żadnych zdjęć. Co się stanie, jeśli appContext nie zostanie zainicjowany? Logika biznesowa tej metody jest z tym zgodna. Jeśli ta sama logika powinna być bardziej rygorystyczna niż zgłoszenie wyjątku, jest to część logiki biznesowej i należy zastosować jawne sprawdzenie wartości null (przypadek 3). Te nowe Java Null-safe API lepiej pasuje tu określenie selektywnie, co oznacza i co nie oznacza być inicjowane za fail-szybko w przypadku błędów programisty.

    Q. Można wykonać nadmiarowy kod i pobrać niepotrzebne zasoby.
    O. Mogłoby to mieć miejsce, gdyby getPhotoByName()spróbował otworzyć połączenie z bazą danych, utworzyć PreparedStatementi użyć nazwiska osoby jako parametru SQL. Podejście do nieznanego pytania daje nieznaną odpowiedź (przypadek 1). Przed pobraniem zasobów metoda powinna sprawdzić parametry i w razie potrzeby zwrócić wynik „nieznany”.

    P: Podejście to ma negatywny wpływ na wydajność z powodu otwarcia zamknięcia próby.
    A. Oprogramowanie powinno być łatwe do zrozumienia i modyfikacji. Dopiero potem można pomyśleć o wydajności i tylko w razie potrzeby! i gdzie potrzeba! ( źródło ) i wiele innych).

    PS. Podejście to będzie tak samo rozsądne, jak zastosowanie oddzielnego kodu obsługi błędów od zasady „zwykłego” kodu w niektórych miejscach. Rozważ następny przykład:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }

    PPS. Dla tych, którzy szybko głosują (i nie tak szybko czytają dokumentację), chciałbym powiedzieć, że nigdy w życiu nie złapałem wyjątku zerowej wartości (NPE). Ale ta możliwość została celowo zaprojektowana przez twórców Javy, ponieważ NPE jest podklasą Exception. Mamy precedens w historii Java kiedy ThreadDeathto Errornie dlatego, że jest rzeczywiście błąd aplikacji, ale tylko dlatego, że nie miał być złapany! Ile NPE wpisuje się być Errorniż ThreadDeath! Ale to nie jest.

  3. Zaznacz „Brak danych” tylko wtedy, gdy wymaga tego logika biznesowa.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }

    i

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }

    Jeśli appContext lub dataSource nie zostanie zainicjowany, nieobsługiwane środowisko wykonawcze NullPointerException zabije bieżący wątek i zostanie przetworzone przez Thread.defaultUncaughtExceptionHandler (w celu zdefiniowania i użycia ulubionego programu rejestrującego lub innego mechanizmu powiadomień). Jeśli nie zostanie ustawiony, ThreadGroup # uncaughtException wypisze stos śledzenia do błędu systemowego. Należy monitorować dziennik błędów aplikacji i otwierać problem Jira dla każdego nieobsługiwanego wyjątku, który w rzeczywistości jest błędem aplikacji. Programista powinien naprawić błąd gdzieś podczas inicjalizacji.

Mychajło Adamowicz
źródło
6
Łapanie NullPointerExceptioni powrót nulljest okropne do debugowania. W końcu i tak otrzymujesz NPE i naprawdę trudno jest ustalić, co było pierwotnie puste.
artbristol
23
Oddałbym głos, gdybym miał reputację. Nie tylko null nie jest konieczny, ale jest dziura w systemie typów. Przypisywanie drzewa do listy jest błędem typu, ponieważ drzewa nie są wartościami typu List; zgodnie z tą samą logiką przypisanie wartości null powinno być błędem typu, ponieważ wartość null nie jest wartością typu Object ani żadnym przydatnym typem w tym zakresie. Nawet człowiek, który wynalazł zero, uważa to za „pomyłkę za miliard dolarów”. Pojęcie „wartości, która może być wartością typu T LUB nic” jest własnym typem i powinno być reprezentowane jako takie (np. Może <T> lub Opcjonalne <T>).
Doval,
2
Od „Może <T> lub Opcjonalnie <T>” nadal musisz pisać kod, if (maybeNull.hasValue()) {...}więc jaka jest różnica if (maybeNull != null)) {...}?
Mychajło Adamowycz
1
„Złapanie NullPointerException i zwrócenie wartości NULL jest trudne do debugowania. W końcu i tak otrzymujesz NPE i naprawdę trudno jest ustalić, co było pierwotnie puste”. Totalnie się zgadzam! W takich przypadkach powinieneś napisać kilkanaście instrukcji „if” lub rzucić NPE, jeśli logika biznesowa sugeruje dane w miejscu, lub użyć null-bezpiecznego operatora z nowej Java. Ale zdarzają się przypadki, gdy nie dbam o to, jaki dokładnie krok da mi wartość zerową. Na przykład obliczenie niektórych wartości dla użytkownika tuż przed wyświetleniem na ekranie, kiedy oczekujesz, że mogą brakować danych.
Mychajło Adamowycz
3
@MykhayloAdamovych: Korzyść Maybe<T>lub Optional<T>nie w przypadku, gdy twój Tmoże być zerowy, ale w przypadku, gdy nigdy nie powinien być zerowy. Jeśli masz typ, który wyraźnie oznacza, że ​​„ta wartość może być zerowa - używaj z rozwagą”, i konsekwentnie używasz i zwracasz taki typ, to za każdym razem, gdy widzisz zwykły stary Tkod, możesz założyć, że nigdy nie jest on zerowy. (Oczywiście byłoby to o wiele bardziej przydatne, gdyby było możliwe do wyegzekwowania przez kompilator.)
cHao
71

Java 7 ma nową java.util.Objectsklasę narzędzi, w której istnieje requireNonNull()metoda. Wszystko to powoduje wyrzucenie a, NullPointerExceptionjeśli jego argument ma wartość NULL, ale trochę czyści kod. Przykład:

Objects.requireNonNull(someObject);
someObject.doCalc();

Ta metoda jest najbardziej przydatna do sprawdzania tuż przed przypisaniem w konstruktorze, gdzie każde jej użycie może zapisać trzy wiersze kodu:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

staje się

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}
Raedwald
źródło
9
W rzeczywistości twój przykład stanowi rozrost kodu: pierwszy wiersz jest zbędny, ponieważ NPE zostałby wyrzucony w drugim wierszu. ;-)
user1050755
1
Prawdziwe. Lepszym przykładem może być druga linia doCalc(someObject).
Stuart Marks
Zależy. Jeśli jesteś autorem doCalc (), sugeruję umieszczenie kontroli w treści tej metody (jeśli to możliwe). A potem najprawdopodobniej wywołasz someObject.someMethod (), gdzie znowu nie ma potrzeby sprawdzania, czy nie ma wartości null. :-)
user1050755
Cóż, jeśli nie jesteś autorem doCalc()i nie od razu wyrzuci NPE, gdy otrzyma wartość NULL, musisz sam sprawdzić, czy jest NULL i wyrzucić NPE. Po to Objects.requireNonNull()jest.
Stuart Marks
7
To nie tylko wzdęcie kodu. Lepiej sprawdzić z góry niż w połowie metody, która powoduje skutki uboczne lub wykorzystuje czas / przestrzeń.
Rob Grant,
51

Ostatecznie jedynym sposobem na całkowite rozwiązanie tego problemu jest użycie innego języka programowania:

  • W Objective-C możesz wykonać odpowiednik wywołania metody nili absolutnie nic się nie wydarzy. To sprawia, że ​​większość kontroli zerowych jest niepotrzebna, ale może znacznie utrudnić diagnozowanie błędów.
  • W Nicei , języku wywodzącym się z Javy, istnieją dwie wersje wszystkich typów: wersja potencjalnie zerowa i wersja niezerowa. Możesz wywoływać metody tylko na typach innych niż null. Typy potencjalnie zerowe można przekonwertować na typy niepuste poprzez jawne sprawdzenie wartości null. Dzięki temu znacznie łatwiej jest wiedzieć, gdzie konieczne są kontrole zerowe, a gdzie nie.
Michael Borgwardt
źródło
Woah ... Najodpowiedniejsza odpowiedź i jej głos jest zaniżony, gdzie jest sprawiedliwość? W Javie null jest zawsze prawidłową wartością. Jest to następstwem Wszystko jest przedmiotem - Null jest wszystkim (oczywiście ignorujemy tutaj prymitywy, ale masz pomysł). Osobiście popieram podejście przyjęte przez Niceę, chociaż możemy to zrobić, aby można było wywoływać metody na typach zerowalnych i promować NPE do sprawdzonych wyjątków.
Musiałoby
4
Nie jestem zaznajomiony z Niceą, ale Kotlin realizuje ten sam pomysł, ma typy zerowalne i niepuste wbudowane w system typów językowych. O wiele bardziej zwięzły niż Opcjonalne lub zerowy wzorzec obiektu.
mtsahakis,
38

Rzeczywiście powszechny „problem” w Javie.

Po pierwsze, moje przemyślenia na ten temat:

Uważam, że źle jest „coś zjeść”, gdy wartość NULL została przekazana, gdy wartość NULL nie jest prawidłową wartością. Jeśli nie wychodzisz z metody z jakimś błędem, oznacza to, że nic nie poszło źle w twojej metodzie, co nie jest prawdą. Wtedy prawdopodobnie zwrócisz null w tym przypadku, a w metodzie odbierającej ponownie sprawdzasz, czy null, i nigdy się nie kończy, i kończysz na „if! = Null” itp.

Tak więc, IMHO, null musi być błędem krytycznym, który uniemożliwia dalsze wykonanie (to znaczy, gdy null nie jest prawidłową wartością).

Sposób rozwiązania tego problemu jest następujący:

Najpierw przestrzegam tej konwencji:

  1. Wszystkie publiczne metody / API zawsze sprawdzają argumenty na wartość NULL
  2. Wszystkie metody prywatne nie sprawdzają wartości null, ponieważ są to metody kontrolowane (po prostu pozwól umrzeć z wyjątkiem wyjątku nullpointer, na wypadek, gdyby nie było to obsługiwane powyżej)
  3. Jedynymi innymi metodami, które nie sprawdzają wartości null, są metody narzędziowe. Są publiczne, ale jeśli zadzwonisz do nich z jakiegoś powodu, wiesz, jakie parametry przekazujesz. To jest jak gotowanie wody w czajniku bez dostarczania wody ...

I wreszcie, w kodzie, pierwszy wiersz metody publicznej wygląda następująco:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Zauważ, że addParam () zwraca self, dzięki czemu możesz dodać więcej parametrów do sprawdzenia.

Metoda validate()rzuci sprawdzone, ValidationExceptionjeśli którykolwiek z parametrów ma wartość NULL (zaznaczone lub niezaznaczone jest bardziej problemem projektowym / smakowym, ale mój ValidationExceptionjest zaznaczony).

void validate() throws ValidationException;

Wiadomość będzie zawierać następujący tekst, jeśli na przykład „plany” są puste:

Napotkano niedozwoloną wartość argumentu null dla parametru [plany]

Jak widać, druga wartość w metodzie addParam () (ciąg) jest potrzebna dla wiadomości użytkownika, ponieważ nie można łatwo wykryć przekazanej nazwy zmiennej, nawet z odbiciem (zresztą nie jest to temat tego postu ...).

I tak, wiemy, że poza tą linią nie napotkamy już wartości zerowej, więc po prostu bezpiecznie wywołujemy metody na tych obiektach.

W ten sposób kod jest czysty, łatwy w utrzymaniu i czytelny.

Oleg
źródło
3
Absolutnie. Aplikacje, które po prostu zgłaszają błąd i awarię, są wyższej jakości, ponieważ nie ma wątpliwości, że nie działają. Aplikacje, które w najlepszym razie połykają błędy, z wdziękiem ulegają degradacji, ale zwykle nie działają w sposób trudny do zauważenia i nie można ich naprawić. A kiedy problem zostanie zauważony, są one znacznie trudniejsze do debugowania.
Paul Jackson
35

Zadanie tego pytania wskazuje, że możesz być zainteresowany strategiami obsługi błędów. Architekt Twojego zespołu powinien zdecydować, jak naprawić błędy. Można to zrobić na kilka sposobów:

  1. pozwól, aby wyjątki przeszły przez nie - złap je w „głównej pętli” lub w innej procedurze zarządzania.

    • sprawdź warunki błędu i postępuj z nimi odpowiednio

Na pewno rzuć okiem również na programowanie zorientowane na aspekt - mają fajne sposoby na wstawienie if( o == null ) handleNull()do twojego kodu bajtowego.

xtofl
źródło
35

Oprócz korzystania assertmożesz użyć następujących opcji:

if (someobject == null) {
    // Handle null here then move on.
}

Jest to nieco lepsze niż:

if (someobject != null) {
    .....
    .....



    .....
}
fastcodejava
źródło
2
Dlaczego? Nie czuj się defensywnie, chciałbym tylko dowiedzieć się więcej o Javie :)
Matthias Meid
9
@Mudu Zasadniczo wolę, aby wyrażenie w instrukcji if było bardziej „pozytywne”, niż „negatywne”. Więc gdybym zobaczył if (!something) { x(); } else { y(); }, byłbym skłonny to tak zrefaktoryzować if (something) { y(); } else { x(); }(chociaż można by argumentować, że != nulljest to bardziej pozytywna opcja ...). Ale co ważniejsze, ważna część kodu nie jest zawinięta wewnątrz {}s, a dla większości metod masz jeden poziom mniej wcięcia. Nie wiem, czy to było rozumowanie fastcodejava, ale to byłoby moje.
MatrixFrog
1
Tak też zwykle się zachowuję. Utrzymuje kod w mojej opinii.
Koray Tugay,
34

Tylko nigdy nie używaj null. Nie pozwól na to.

W moich klasach większość pól i zmiennych lokalnych ma niepuste wartości domyślne i wszędzie w kodzie dodam instrukcje kontraktowe (zawsze włączone), aby upewnić się, że jest ono egzekwowane (ponieważ jest bardziej zwięzłe i bardziej wyraziste niż na to pozwalanie wymyślić jako NPE, a następnie rozwiązać numer linii itp.).

Po przyjęciu tej praktyki zauważyłem, że problemy same się rozwiązały. Łapiesz rzeczy na wcześniejszym etapie procesu projektowania przez przypadek i zdajesz sobie sprawę, że masz słaby punkt ... a co ważniejsze ... pomaga to uwzględnić problemy różnych modułów, różne moduły mogą sobie "ufać" i nie zaśmieca kod z if = null elsekonstrukcjami!

Jest to programowanie defensywne i skutkuje znacznie czystszym kodem na dłuższą metę. Zawsze odkażaj dane, np. Tutaj poprzez egzekwowanie sztywnych standardów, a problemy znikną.

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

Kontrakty są jak testy mini-jednostek, które zawsze trwają, nawet w produkcji, a kiedy coś się nie powiedzie, wiesz dlaczego, a nie losowy NPE, musisz jakoś wymyślić.

iangreen
źródło
dlaczego miałoby to być przegłosowane? z mojego doświadczenia wynika, że ​​jest to znacznie lepsze od innych podejść, chciałbym wiedzieć, dlaczego nie
ianpojman
Zgadzam się, że to podejście zapobiega problemom związanym z zerami, zamiast naprawiania ich przez nakrapianie kodu zerowego wszędzie.
ChrisBlom
7
Problem z tym podejściem polega na tym, że jeśli nazwa nigdy nie jest ustawiona, ma ona wartość „<unnown>”, która zachowuje się jak ustawiona wartość. Teraz powiedzmy, że muszę sprawdzić, czy nazwa nigdy nie została ustawiona (nieznana), muszę wykonać porównanie ciągów znaków ze specjalną wartością „<unknown>”.
Steve Kuo,
1
Naprawdę dobry punkt Steve. To, co często robię, ma tę wartość jako stałą, np. Publiczny statyczny końcowy ciąg UNSET = "__ unset" ... prywatny ciąg String = UNSET ... następnie prywatny boolean isSet () {return UNSET.equals (pole); }
ianpojman
IMHO, jest to implementacja Wzorca Null Object z samodzielną implementacją Opcjonalnego (Kontraktu). Jak zachowuje się w klasie klasy trwałości? Nie widzę zastosowania w tym przypadku.
Kurapika,
31

Guava, bardzo przydatna biblioteka podstawowa Google, ma przyjemny i użyteczny interfejs API pozwalający uniknąć wartości zerowych. Uważam, że UsingAndAvoidingNullExplained jest bardzo pomocny.

Jak wyjaśniono w wiki:

Optional<T>jest sposobem na zastąpienie zerowego odniesienia T wartością inną niż null. Opcjonalne może albo zawierać niepuste odwołanie T (w którym to przypadku mówimy, że odwołanie jest „obecne”), albo może nie zawierać niczego (w którym to przypadku mówimy, że odwołanie jest „nieobecne”). Nigdy nie mówi się, że „zawiera null”.

Stosowanie:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Murat
źródło
@CodyGuldner Right, Cody. Podałem odpowiedni cytat z linku, aby dać więcej kontekstu.
Murat Derya Özen
25

Jest to bardzo częsty problem dla każdego programisty Java. Tak więc istnieje oficjalne wsparcie w Javie 8, aby rozwiązać te problemy bez zaśmieconego kodu.

Java 8 została wprowadzona java.util.Optional<T>. Jest to pojemnik, który może zawierać lub nie mieć wartości innej niż zero. Java 8 zapewnił bezpieczniejszy sposób obsługi obiektu, którego wartość w niektórych przypadkach może być zerowa. Jest inspirowany pomysłami Haskell i Scali .

W skrócie, klasa Optional zawiera metody jawnego radzenia sobie z przypadkami, w których wartość jest obecna lub nieobecna. Jednak przewaga w porównaniu z referencjami zerowymi polega na tym, że Opcjonalna klasa <T> zmusza do myślenia o przypadku, gdy wartość nie jest obecna. W rezultacie możesz zapobiec niezamierzonym wyjątkom wskaźnika zerowego.

W powyższym przykładzie mamy fabrykę usług domowych, która zwraca uchwyt do wielu urządzeń dostępnych w domu. Ale te usługi mogą, ale nie muszą być dostępne / funkcjonalne; oznacza to, że może to spowodować wyjątek NullPointerException. Zamiast dodawać nullif warunek przed użyciem jakiejkolwiek usługi, zawińmy go w Opcjonalne <Service>.

OWIJANIE DO OPCJI <T>

Rozważmy metodę uzyskania referencji usługi z fabryki. Zamiast zwracać odwołanie do usługi, zawiń je Opcjonalnie. Informuje użytkownika interfejsu API, że zwrócona usługa może, ale nie musi być dostępna / funkcjonalna, używać defensywnie

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

Jak widać, Optional.ofNullable()zapewnia łatwy sposób na zapakowanie referencji. Istnieją inne sposoby uzyskania odniesienia do Opcjonalnego Optional.empty():Optional.of() . Jeden do zwrócenia pustego obiektu zamiast przestrajania wartości null, a drugi do zawinięcia odpowiednio obiektu, który nie ma wartości zerowej.

W JAKI SPOSÓB DOKŁADNIE POMAGA UNIKAĆ KONTROLI NULL?

Po zawinięciu obiektu referencyjnego, Opcjonalne udostępnia wiele przydatnych metod do wywoływania metod na opakowanym odwołaniu bez NPE.

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

Optional.ifPresent wywołuje danego konsumenta z referencją, jeśli jest to wartość inna niż null. W przeciwnym razie nic nie robi.

@FunctionalInterface
public interface Consumer<T>

Reprezentuje operację, która akceptuje pojedynczy argument wejściowy i nie zwraca żadnego wyniku. W przeciwieństwie do większości innych interfejsów funkcjonalnych, oczekuje się, że Konsument będzie działał poprzez skutki uboczne. Jest tak czysty i łatwy do zrozumienia. W powyższym przykładzie koduHomeService.switchOn(Service) jest wywoływane, jeśli odwołanie Opcjonalne wstrzymanie ma wartość inną niż null.

Bardzo często używamy operatora trójskładnikowego do sprawdzania stanu zerowego i zwracamy wartość alternatywną lub domyślną. Opcjonalny zapewnia inny sposób obsługi tego samego warunku bez sprawdzania wartości NULL. Optional.orElse (defaultObj) zwraca defaultObj, jeśli opcja ma wartość pustą. Użyjmy tego w naszym przykładowym kodzie:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

Teraz HomeServices.get () robi to samo, ale w lepszy sposób. Sprawdza, czy usługa jest już zainicjowana. Jeśli tak, zwróć to samo lub utwórz nową nową usługę. Opcjonalne <T>. Lub Else (T) pomaga zwrócić wartość domyślną.

Wreszcie, oto nasz NPE oraz zerowy kod bez czeku:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

Cały post to NPE i kod zerowy Null… Naprawdę? .

Yogesh Devatraj
źródło
W powyższym kodzie znajduje się znak zerowy - if(homeServices != null) {który można zmienić na homeServices.ifPresent(h -> //action);
KrishPrabakar
23

Lubię artykuły z Nat Pryce. Oto linki:

W artykułach znajduje się również link do repozytorium Git dla typu Java Może typu, który uważam za interesujący, ale nie sądzę, aby sam mógł zmniejszyć wzdęcia kodu sprawdzającego. Po przeprowadzeniu pewnych badań w Internecie, myślę, że ! = Wzdęcia z zerowym kodem można zmniejszyć głównie przez staranne zaprojektowanie.

Mr Palo
źródło
Michael Feathers napisał krótki i interesujący tekst o podejściach takich jak ten, o którym wspomniałeś: manuelp.newsblur.com/site/424
ivan.aguirre
21

Próbowałem, NullObjectPatternale dla mnie nie zawsze jest to najlepsza droga. Czasami „brak działania” jest niewłaściwy.

NullPointerExceptionjest wyjątkiem Runtime, co oznacza, że ​​jest to wina programistów i przy wystarczającym doświadczeniu informuje dokładnie, gdzie jest błąd.

Teraz do odpowiedzi:

Postaraj się, aby wszystkie twoje atrybuty i ich akcesory były jak najbardziej prywatne lub unikaj ich w ogóle ujawniania klientom. Oczywiście możesz mieć wartości argumentów w konstruktorze, ale zmniejszając zakres, nie pozwalasz klasie klienta przekazać niepoprawnej wartości. Jeśli musisz zmodyfikować wartości, zawsze możesz utworzyć nową object. Sprawdzasz wartości w konstruktorze tylko raz, a w pozostałych metodach możesz być prawie pewien, że wartości nie są równe null.

Oczywiście doświadczenie jest lepszym sposobem na zrozumienie i zastosowanie tej sugestii.

Bajt!

OscarRyz
źródło
19

Prawdopodobnie najlepszą alternatywą dla Java 8 lub nowszej jest użycie tej Optionalklasy.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

Jest to szczególnie przydatne w przypadku długich łańcuchów możliwych wartości zerowych. Przykład:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Przykład, jak zgłosić wyjątek na wartość NULL:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 wprowadziła Objects.requireNonNullmetodę, która może się przydać, gdy należy sprawdzić coś pod kątem braku wartości. Przykład:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();
Raghu K Nair
źródło
17

Czy mogę udzielić bardziej ogólnej odpowiedzi!

My zwykle zmierzyć się z tym problem, gdy metody uzyskać parametry w taki sposób, że nie oczekiwano (metoda złe połączenie jest winą programisty). Na przykład: spodziewasz się uzyskać obiekt, zamiast tego otrzymujesz zero. Oczekujesz, że otrzymasz Ciąg z co najmniej jednym znakiem, zamiast tego otrzymasz Pusty Ciąg ...

Więc nie ma różnicy między:

if(object == null){
   //you called my method badly!

}

lub

if(str.length() == 0){
   //you called my method badly again!
}

Oboje chcą upewnić się, że otrzymaliśmy prawidłowe parametry, zanim wykonamy inne funkcje.

Jak wspomniano w niektórych innych odpowiedziach, aby uniknąć powyższych problemów, można postępować zgodnie z wzorcem Projektowanie według umowy . Proszę zobaczyć http://en.wikipedia.org/wiki/Design_by_contract .

Aby zaimplementować ten wzorzec w java, możesz użyć podstawowych adnotacji java, takich jak javax.annotation.NotNull lub użyć bardziej wyrafinowanych bibliotek, takich jak Hibernate Validator .

Tylko próbka:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

Teraz możesz bezpiecznie opracować podstawową funkcję swojej metody bez konieczności sprawdzania parametrów wejściowych, chronią one twoje metody przed nieoczekiwanymi parametrami.

Możesz pójść o krok dalej i upewnić się, że w aplikacji można utworzyć tylko poprawne pojęcia. (próbka ze strony walidatora hibernacji)

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}
Alireza Fattahi
źródło
javaxz definicji nie jest „rdzeniem Java”.
Tomas
17

Bardzo lekceważę odpowiedzi sugerujące użycie zerowych obiektów w każdej sytuacji. Ten wzorzec może zerwać kontrakt i pogłębiać problemy coraz głębiej zamiast je rozwiązywać, nie wspominając, że niewłaściwe użycie spowoduje utworzenie kolejnego stosu kodu płyty, który będzie wymagał przyszłej konserwacji.

W rzeczywistości, jeśli coś zwróconego z metody może być zerowe i kod wywołujący musi podjąć decyzję w tej sprawie, powinno być wcześniejsze wywołanie zapewniające stan.

Należy również pamiętać, że wzorzec zerowy obiektów będzie wymagał pamięci, jeśli zostanie użyty bez opieki. W tym celu - instancja obiektu NullObject powinna być współużytkowana między właścicielami, a nie dla każdej z nich stanowić instancję typu unigue.

Nie zalecałbym również używania tego wzorca, w którym typ ma być pierwotną reprezentacją typu - jak jednostki matematyczne, które nie są skalarami: wektory, macierze, liczby zespolone i obiekty POD (Plain Old Data), które mają utrzymywać stan w postaci wbudowanych typów Java. W tym drugim przypadku wywołanie metod gettera zakończy się arbitralnie. Na przykład, co powinna zwrócić metoda NullPerson.getName ()?

Warto rozważyć takie przypadki, aby uniknąć absurdalnych wyników.

rev. Luke1985
źródło
Rozwiązanie z „hasBackground ()” ma jedną wadę - nie jest bezpieczne dla wątków. Jeśli chcesz wywołać dwie metody zamiast jednej, musisz zsynchronizować całą sekwencję w środowisku wielowątkowym.
pkalinow
@pkalinow Zrobiłeś wymyślony przykład, aby podkreślić, że to rozwiązanie ma wadę. Jeśli kod nie ma działać w aplikacji wielowątkowej, nie ma wady. Mógłbym umieścić na tobie prawdopodobnie 90% twojego kodu, który nie jest bezpieczny dla wątków. Nie mówimy tutaj o tym aspekcie kodu, mówimy o wzorcu projektowym. A wielowątkowość to temat sam w sobie.
luke1985
Oczywiście w aplikacji jednowątkowej to nie problem. Dałem ten komentarz, ponieważ czasami jest to problem.
pkalinow
@pkalinow Jeśli przestudiujesz ten temat bliżej, przekonasz się, że wzorzec projektowy Null Object nie rozwiąże problemów z wielowątkowością. To nie ma znaczenia. I szczerze mówiąc, znalazłem miejsca, w których ten wzór dobrze by pasował, więc moja oryginalna odpowiedź jest trochę błędna.
luke1985
16
  1. Nigdy nie inicjuj zmiennych do wartości null.
  2. Jeśli (1) nie jest możliwe, zainicjuj wszystkie kolekcje i tablice, aby opróżnić kolekcje / tablice.

Robiąc to we własnym kodzie, możesz uniknąć! = Kontrola zerowa.

Przez większość czasu kontrole zerowe wydają się chronić pętle nad kolekcjami lub tablicami, więc po prostu zainicjuj je puste, nie będziesz potrzebować żadnych kontroli zerowych.

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

Jest w tym niewielki narzut, ale warto dla czystszego kodu i mniej NullPointerExceptions.

Stuart Axon
źródło
3
+1 Zgadzam się z tym. Nigdy nie powinieneś zwracać połowy zainicjowanych obiektów. Kod związany z Jaxb i kodem fasoli jest do tego niezaprzeczalny. To zła praktyka. Wszystkie kolekcje powinny być inicjowane, a wszystkie obiekty powinny istnieć (najlepiej) bez odwołań zerowych. Rozważ obiekt, który zawiera kolekcję. Sprawdzanie, czy obiekt nie jest pusty, czy kolekcja nie ma wartości zerowej i czy kolekcja nie zawiera obiektów zerowych, jest nierozsądne i głupie.
ggb667,
15

Jest to najczęstszy błąd występujący u większości programistów.

Mamy na to wiele sposobów.

Podejście 1:

org.apache.commons.lang.Validate //using apache framework

notNull (Object object, komunikat String)

Podejście 2:

if(someObject!=null){ // simply checking against null
}

Podejście 3:

@isNull @Nullable  // using annotation based validation

Podejście 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}
Sireesh Yarlagadda
źródło
Ogłoszenie. 4. Nie jest to bardzo przydatne - gdy sprawdzasz, czy wskaźnik ma wartość NULL, prawdopodobnie chcesz wywołać na nim metodę. Wywołanie metody na wartości null daje to samo zachowanie - NullPointerException.
pkalinow
11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}
tltester
źródło
2
Co jest nie tak z tą metodą, myślę, że @tltester chce po prostu podać wartość domyślną, jeśli jest ona pusta, co ma sens.
Sawyer,
1
Jest taka metoda A w Apache Commons Lang: ObjectUtils.defaultIfNull(). Jest jeszcze jedna ogólna: ObjectUtils.firstNonNull(), które mogą być wykorzystane do realizacji poniżającego strategii:firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
ivant