web-dev-qa-db-ja.com

メソッドチェーンでnull値のチェックを避ける方法は?

一部の値がnullかどうかを確認する必要があります。 nullでない場合は、変数をtrueに設定するだけです。ここにはelseステートメントはありません。このような状態チェックが多すぎます。

すべてのメソッドの戻り値をチェックせずにこのnullチェックを処理する方法はありますか?

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

変数を直接チェックして、NullpointerExceptionを無視することを考えました。これは良い習慣ですか?

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

いいえ、一般的にJavaでは、参照をnullチェックする代わりにNPEをキャッチすることはお勧めできません。

必要に応じて、この種のものに Optional を使用できます。

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

または単に

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

isValidがチェックすることになっているのがこれだけである場合。

66
khelwood

ここではOptionalを使用できますが、各ステップで1つのOptionalオブジェクトを作成します。

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

オブジェクト指向のアプローチは、isValidメソッドをCountryおよび他のクラスに配置することです。 nullチェックの量は減りませんが、各メソッドには1つしかなく、繰り返しはしません。

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

これには、国が使用されているすべての場所で検証が同じであるという前提がありますが、通常はそうです。そうでない場合、メソッドはhasStudent()という名前にする必要がありますが、これはあまり一般的ではなく、CountryのSchoolインターフェース全体を複製するリスクがあります。たとえば、別の場所ではhasTeacher()またはhasCourse()が必要になる場合があります。

別のアプローチは、nullオブジェクトを使用することです。

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

  private City city = City.NO_CITY;

  // etc.
}

このケースが望ましいかどうかはわかりません(厳密には、すべての変更メソッドをオーバーライドするためにサブクラスが必要です)、Java 8答えますが、私はそれをより完全に受け入れることをお勧めします:

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

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

NullオブジェクトとNullableの両方は、nullの代わりに常に使用する場合にのみ機能します(フィールドの初期化に注意してください)。それ以外の場合は、nullチェックが必要です。したがって、このオプションはnullを回避しますが、他の場所でnullチェックを減らすためにコードはより冗長になります。

もちろん、可能な場合は(オプションではなく)コレクションを使用するのが正しい設計かもしれません。国には市のセット、市には学校のセット、学生のセットなどがあります。

7
Walter Laan

Optionalの他の細かい使用方法の代わりに、Supplier<Object> var-argsをパラメーターとして使用するユーティリティメソッドを使用することもできます。
チェックするオブジェクトにネストされたレベルは多くありませんが、チェックするフィールドは多いため、これは理にかなっています。
さらに、nullが検出されたときに何かをログ/処理するように簡単に変更できます。

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

トレースを追加したい場合、次のように書くことができます。

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には、たとえば Kotlinのnull安全性 などの「null安全な」操作がありません。

次のいずれかを実行できます。

  • nPEをキャッチして無視する
  • すべての参照を手動で確認してください
  • 他の回答ごとにオプションを使用
  • xLSTのような何らかのツールを使用する

それ以外の場合、ドメインオブジェクトを制御できる場合は、必要な情報がトップレベルオブジェクトから利用できるようにクラスを再設計できます(Countryクラスにすべてのnullチェックを実行させる...)

4
vikingsteve

また、以下の投稿で説明しているように、Javaのオプションよりも優れており、より豊富なAPIを備えているvavrのオプションを見ることができます。

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

0
Gulats