web-dev-qa-db-fra.com

Comment éviter de rechercher des valeurs NULL dans le chaînage de méthodes?

Je dois vérifier si une valeur est nulle ou non. Et si ce n'est pas nul, définissez simplement une variable sur true. Il n'y a pas d'autre déclaration ici. J'ai trop de vérifications d'état comme celle-ci.

Existe-t-il un moyen de gérer ces contrôles null sans vérifier toutes les valeurs de retour de méthode?

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

Je pensais vérifier directement la variable et ignorer NullpointerException. Est-ce une bonne pratique?

try{
    if(country.getCity().getSchool().getStudent().getInfo().... != null)
} catch(NullPointerException ex){
    //dont do anything.
}
43
user9521067

Non, ce n'est généralement pas une bonne pratique dans Java d'attraper un NPE au lieu de vérifier vos références à zéro.

Vous pouvez utiliser Optional pour ce genre de chose si vous préférez:

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

ou simplement

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

si c'est tout ce que isValid est censé vérifier.

66
khelwood

Vous pouvez utiliser Optional ici, mais un objet facultatif est créé à chaque étape.

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();
15
user7

L'approche orientée objet consiste à mettre la méthode isValid dans Country et dans les autres classes. Cela ne réduit pas le nombre de contrôles nuls, mais chaque méthode en a un et vous ne les répétez pas.

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

Cela suppose que la validation est la même partout où votre pays est utilisé, mais c'est généralement le cas. Sinon, la méthode doit être nommée hasStudent (), mais elle est moins générale et vous courez le risque de dupliquer toute l'interface School dans Country. Par exemple, ailleurs, vous aurez peut-être besoin de hasTeacher () ou hasCourse ().

Une autre approche consiste à utiliser des objets nuls:

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

  private City city = City.NO_CITY;

  // etc.
}

Je ne suis pas sûr qu'il soit préférable dans ce cas (vous auriez strictement besoin d'une sous-classe pour écraser toutes les méthodes de modification), la méthode Java 8 serait d'utiliser la méthode Optional comme méthode dans l'autre méthode). réponses, mais je suggérerais de l’embrasser plus complètement:

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

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

Les objets null et Nullable ne fonctionnent que si vous les utilisez toujours à la place de null (notez l'initialisation du champ), sinon vous aurez toujours besoin des vérifications NULL. Donc, cette option évite les valeurs nulles, mais votre code devient plus détaillé et réduit les contrôles nulles ailleurs.

Bien sûr, la conception correcte peut consister à utiliser des collections dans la mesure du possible (au lieu de facultatif). Un pays a un ensemble de ville, une ville a un ensemble d'écoles, qui a un ensemble d'élèves, etc.

7
Walter Laan

Comme alternative à une autre utilisation fine de Optional, nous pourrions également utiliser une méthode utilitaire avec un Supplier<Object> var-args en tant que paramètre.
Cela a du sens car nous n'avons pas beaucoup de niveaux imbriqués dans l'objet à vérifier mais beaucoup de champs à vérifier.
En outre, il peut facilement être modifié pour enregistrer/gérer quelque chose lorsqu'un null est détecté.

    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;
}

Supposons que vous souhaitiez ajouter des traces, vous pouvez donc écrire:

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
davidxxx

Java n'a pas d'opération "null-safe", comme par exemple sécurité de Kotlin

Vous pouvez soit:

  • attraper le NPE et l'ignorer
  • vérifier toutes les références manuellement
  • utiliser facultatif selon les autres réponses
  • utiliser une sorte d'outillage comme XLST

Sinon, si vous avez le contrôle sur les objets du domaine, vous pouvez redéfinir vos classes afin que les informations dont vous avez besoin soient disponibles à partir de l'objet de niveau supérieur (make Country class effectue toutes les vérifications de null ...).

4
vikingsteve

Vous pouvez également regarder Option de vavr qui, comme décrit dans les articles ci-dessous, est meilleur que celui de Java facultatif et possède une API beaucoup plus riche.

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

0
Gulats