Jak uniknąć sprawdzania wartości null w łańcuchowaniu metod? [duplikat]

To pytanie ma już odpowiedź tutaj:

Muszę sprawdzić czy jakaś wartość jest null czy nie. A jeśli nie jest null to Ustaw jakąś zmienną na true. Nie ma tu żadnego innego oświadczenia. Mam za dużo takich testów.

Czy Jest jakiś sposób, aby obsłużyć te kontrole null bez sprawdzania wszystkich wartości zwracanych przez metody?

if(country != null && country.getCity() != null && country.getCity().getSchool() != null && country.getCity().getSchool().getStudent() != null .....) {
    isValid = true;
}

Myślałem, że bezpośrednio sprawdzam zmienną i ignoruję NullpointerException. Czy to dobra praktyka?

try{
    if(country.getCity().getSchool().getStudent().getInfo().... != null)
} catch(NullPointerException ex){
    //dont do anything.
}
Author: Ari Brodsky, 2018-03-20

6 answers

Nie, ogólnie nie jest dobrą praktyką w Javie łapanie NPE zamiast sprawdzania referencji null.

Możesz użyć Optional do tego typu rzeczy jeśli wolisz:

if (Optional.ofNullable(country)
            .map(Country::getCity)
            .map(City::getSchool)
            .map(School::getStudent)
            .isPresent()) {
    isValid = true;
}

Lub po prostu

boolean isValid = Optional.ofNullable(country)
                          .map(Country::getCity)
                          .map(City::getSchool)
                          .map(School::getStudent)
                          .isPresent();

Jeśli to wszystko, co isValid powinno być sprawdzane.

 56
Author: khelwood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 08:31:18

Możesz użyć Optional tutaj, ale tworzy on jeden opcjonalny obiekt na każdym kroku.

boolean isValid = Optional.ofNullable(country)
    .map(country -> country.getCity()) //Or use method reference Country::getCity
    .map(city -> city.getSchool())
    .map(school -> school.getStudent())
    .map(student -> true)
    .orElse(false);

//OR
boolean isValid = Optional.ofNullable(country)
                      .map(..)
                      ....
                      .isPresent();
 13
Author: user7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 09:10:37

Podejście obiektowe polega na umieszczeniu metody isValid w Country i innych klasach. Nie zmniejsza to ilości sprawdzeń null, ale każda metoda ma tylko jedną i nie powtarzasz ich.

public boolean isValid() {
  return city != null && city.isValid();
}

Zakłada się, że Walidacja jest taka sama wszędzie, gdzie używany jest twój kraj, ale zazwyczaj tak jest. Jeśli nie, metoda powinna być nazwana hasStudent (), ale jest to mniej ogólne i istnieje ryzyko powielenia całego interfejsu szkolnego w danym kraju. Na przykład, w innym miejscu możesz potrzebować hasTeacher () lub hasCourse ().

Innym podejściem jest użycie obiektów null:

public class Country {
  public static final Country NO_COUNTRY = new Country();

  private City city = City.NO_CITY;

  // etc.
}

Nie jestem pewien, czy jest to lepsze w tym przypadku (ściśle potrzebujesz podklasy, aby nadpisać wszystkie metody modyfikacji), sposobem Java 8 byłoby przejście z opcjonalną metodą jako w innych odpowiedziach, ale sugerowałbym, aby objąć ją pełniej:

private Optional<City> city = Optional.ofNullable(city);

public Optional<City> getCity() {
   return city;
}

Zarówno dla obiektów null, jak i Nullable działają tylko wtedy, gdy zawsze ich używasz zamiast null (zwróć uwagę na pole inicjalizacja), w przeciwnym razie nadal potrzebujesz sprawdzeń null. Ta opcja pozwala uniknąć null, ale kod staje się bardziej wyrazisty, aby zmniejszyć kontrole null w innych miejscach.

Oczywiście poprawnym projektem może być użycie zbiorów tam, gdzie jest to możliwe (zamiast opcjonalnego). Kraj ma zestaw miasta, Miasto ma zestaw szkół, który ma zestaw uczniów itp.

 6
Author: Walter Laan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 12:28:03

Jako alternatywa dla innych drobnych zastosowań Optional, możemy również użyć metody użytkowej z Supplier<Object> var-args jako parametrem.
Ma to sens, ponieważ nie mamy wielu zagnieżdżonych poziomów w obiekcie do sprawdzenia, ale wiele pól do sprawdzenia.
Poza tym może być łatwo zmodyfikowany, aby logować / obsługiwać coś, ponieważ null jest wykrywany.

    boolean isValid = isValid(() -> address, // first level
                              () -> address.getCity(),   // second level
                              () -> address.getCountry(),// second level
                              () -> address.getStreet(), // second level
                              () -> address.getZip(),    // second level
                              () -> address.getCountry() // third level
                                           .getISO()


@SafeVarargs
public static boolean isValid(Supplier<Object>... suppliers) {
    for (Supplier<Object> supplier : suppliers) {
        if (Objects.isNull(supplier.get())) {
            // log, handle specific thing if required
            return false;
        }
    }
    return true;
}

Załóżmy, że chciałbyś dodać jakieś ślady, możesz tak napisać:

boolean isValid = isValid(  Arrays.asList("address", "city", "country",
                                          "street", "zip", "Country ISO"),
                            () -> address, // first level
                            () -> address.getCity(),   // second level
                            () -> address.getCountry(),// second level
                            () -> address.getStreet(), // second level
                            () -> address.getZip(),    // second level
                            () -> address.getCountry() // third level
                                         .getISO()
                         );


@SafeVarargs
public static boolean isValid(List<String> fieldNames, Supplier<Object>... suppliers) {
    if (fieldNames.size() != suppliers.length){
         throw new IllegalArgumentException("...");
    }
    for (int i = 0; i < suppliers.length; i++) {
        if (Objects.isNull(suppliers.get(i).get())) {
            LOGGER.info( fieldNames.get(i) + " is null");
            return false;
        }
    }
    return true;
}
 5
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 04:46:04

Java nie ma operacji "null-safe", jak na przykład Kotlin ' s null safety

Możesz:

  • Złap NPE i zignoruj go
  • sprawdź wszystkie referencje ręcznie
  • użyj opcji jak w pozostałych odpowiedziach
  • użyj jakiegoś narzędzia, takiego jak XLST

W przeciwnym razie, jeśli masz kontrolę nad obiektami domeny, możesz przeprojektować swoje klasy tak, aby potrzebne informacje były dostępne z obiektu najwyższego poziomu (make Country class sprawdźcie wszystko...)

 4
Author: vikingsteve,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 09:33:36

Możesz również spojrzeć na opcję vavr, która jak opisują poniższe posty, jest lepsza niż opcjonalna Java i ma znacznie bogatsze API.

Https://softwaremill.com/do-we-have-better-option-here/

 0
Author: Gulats,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 14:15:29