web-dev-qa-db-ja.com

Java:ネストされたクラスでnullのチェックを回避(ディープnullチェック)

クラスファミリーがあると想像してください。個人のリストが含まれています。各(クラス)Personには(クラス)アドレスが含まれます。各(クラス)アドレスには、(クラス)PostalCodeが含まれています。 「中間」クラスはnullにすることができます。

それで、すべてのステップでnullをチェックする必要なく、PostalCodeにアクセスする簡単な方法はありますか?つまり、次のデイジーチェーンコードを回避する方法はありますか? 「ネイティブ」ではないことを知っているJava解決策ですが、ライブラリや何かを知っている人がいるかどうかを望んでいました(Commons&Guavaをチェックして何も表示されなかった)

if(family != null) {
    if(family.getPeople() != null) {
        if(family.people.get(0) != null) {
            if(people.get(0).getAddress() != null) {
                if(people.get(0).getAddress().getPostalCode() != null) {
                    //FINALLY MADE IT TO DO SOMETHING!!!
                }
            }
        }
    }
}

いいえ、構造を変更することはできません。それは私が制御できないサービスからのものです。

いいえ、Groovyは使用できません。便利な「Elvis」演算子です。

いいえ、待ちませんJava 8:D

私がこのようなコードを書くことにうんざりする最初の開発者であるとは信じられませんが、解決策を見つけることができませんでした。

アイデア?

ありがとう

-
llappall

38
llappall

コードは次のように動作します

if(family != null &&
  family.getPeople() != null &&
  family.people.get(0) != null && 
  family.people.get(0).getAddress() != null &&
  family.people.get(0).getAddress().getPostalCode() != null) { 
       //My Code
}

短絡評価のおかげで、これも安全です。最初の条件が偽の場合、2番目の条件は評価されないため、 2番目がfalseの場合、3番目は評価されません。.もしそうであれば、NPEを取得できません。

14
amit

次の用途に使用できます。

product.getLatestVersion().getProductData().getTradeItem().getInformationProviderOfTradeItem().getGln();

オプションの同等物:

Optional.ofNullable(product).map(
            Product::getLatestVersion
        ).map(
            ProductVersion::getProductData
        ).map(
            ProductData::getTradeItem
        ).map(
            TradeItemType::getInformationProviderOfTradeItem
        ).map(
            PartyInRoleType::getGln
        ).orElse(null);
7
Robert

あなたが得ることができる最も近いものは、条件文のショートカットルールを利用することです:

if(family != null && family.getPeople() != null && family.people.get(0) != null  && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) {
                    //FINALLY MADE IT TO DO SOMETHING!!!

}

ところで、事前に条件をテストする代わりに例外をキャッチするのは恐ろしい考えです。

6
Paul Tomblin

まれな場合は、nullチェックを無視してNullPointerExceptionに依存することができます。可能性のあるパフォーマンスの問題が原因で「まれ」(通常、高価になる可能性のあるスタックトレースが記入されます)。

それ以外は1)nullをチェックしてそのコードをクリーンアップする特定のヘルパーメソッド、または2)リフレクションと文字列を使用して一般的なアプローチを行う:

checkNonNull(family, "people[0].address.postalcode")

実装は演習として残しました。

この投稿はほぼ5年前のものですが、NullPointerExceptionsを処理する方法についての古くからの質問に対する別の解決策があるかもしれません。

一言で言えば:

_end: {
   List<People> people = family.getPeople();            if(people == null || people.isEmpty()) break end;
   People person = people.get(0);                       if(person == null) break end;
   Address address = person.getAddress();               if(address == null) break end;
   PostalCode postalCode = address.getPostalCode();     if(postalCode == null) break end;

   System.out.println("Do stuff");
}
_

まだ多くのレガシーコードが使用されているため、Java 8およびOptionalを使用することは常にオプションであるとは限りません。

深くネストされたクラス(JAXB、SOAP、JSON、それに名前を付ける...)があり、 Law of Demeter が適用されていない場合は、基本的にすべてをチェックして、可能なNPEがあるかどうかを確認する必要があります潜んでいる。

私の提案するソリューションは読みやすさを追求しているため、少なくとも3つ以上のネストされたクラスが含まれていない場合は使用しないでください(ネストされていると言っても、正式なコンテキストでは ネストされたクラス を意味しません)。 。コードは書かれている以上に読み取られるため、コードの左側を一目見ると、深くネストされたif-elseステートメントを使用するよりもその意味が明確になります。

Else部分が必要な場合は、次のパターンを使用できます。

_boolean prematureEnd = true;

end: {
   List<People> people = family.getPeople();            if(people == null || people.isEmpty()) break end;
   People person = people.get(0);                       if(person == null) break end;
   Address address = person.getAddress();               if(address == null) break end;
   PostalCode postalCode = address.getPostalCode();     if(postalCode == null) break end;

   System.out.println("Do stuff");
   prematureEnd = false;
}

if(prematureEnd) {
    System.out.println("The else part");
}
_

特定のIDEは、ユーザーに指示しない限り、このフォーマットを解除します( この質問 を参照)。

条件文は反転する必要があります。コードを継続する必要があるときではなく、いつ中断するかをコードに伝えます。

もう1つ、コードはまだ壊れやすい傾向があります。コードの最初の行としてif(family.getPeople() != null && !family.getPeople().isEmpty())を使用する必要があります。そうしないと、空のリストがNPEをスローします。

1
darioo

それほどクールなアイデアではありませんが、例外をキャッチするのはどうですか?

    try 
    {
        PostalCode pc = people.get(0).getAddress().getPostalCode();
    }
    catch(NullPointerException ex)
    {
        System.out.println("Gotcha");
    }
1
MByD

場合によっては、Java8を使用している場合は使用できます。

resolve(() -> people.get(0).getAddress().getPostalCode());
    .ifPresent(System.out::println);

:
public static <T> Optional<T> resolve(Supplier<T> resolver) {
    try {
        T result = resolver.get();
        return Optional.ofNullable(result);
    }
    catch (NullPointerException e) {
        return Optional.empty();
    }
}

REF: nullチェックを回避する

1

Nullを使用する代わりに、「nullオブジェクト」デザインパターンのいくつかのバージョンを使用できます。例えば:

public class Family {
    private final PersonList people;
    public Family(PersonList people) {
        this.people = people;
    }

    public PersonList getPeople() {
        if (people == null) {
            return PersonList.NULL;
        }
        return people;
    }

    public boolean isNull() {
        return false;
    }

    public static Family NULL = new Family(PersonList.NULL) {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}


import Java.util.ArrayList;

public class PersonList extends ArrayList<Person> {
    @Override
    public Person get(int index) {
        Person person = null;
        try {
            person = super.get(index);
        } catch (ArrayIndexOutOfBoundsException e) {
            return Person.NULL;
        }
        if (person == null) {
            return Person.NULL;
        } else {
            return person;
        }
    }
    //... more List methods go here ...

    public boolean isNull() {
        return false;
    }

    public static PersonList NULL = new PersonList() {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}

public class Person {
    private Address address;

    public Person(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        if (address == null) {
            return Address.NULL;
        }
        return address;
    }
    public boolean isNull() {
        return false;
    }

    public static Person NULL = new Person(Address.NULL) {
        @Override
        public boolean isNull() {
            return true;
        }
    };
}

etc etc etc

次に、ifステートメントは次のようになります。

if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...}

それは次のように最適ではありません:

  • すべてのクラスに対してNULLオブジェクトを作成するのに行き詰まっています。
  • これらのオブジェクトをジェネリックにするのは難しいので、使用したい各リスト、マップなどのnullオブジェクトバージョンを作成するのに行き詰まっています。
  • サブクラス化と使用するNULLに潜在的にいくつかの面白い問題があります。

しかし、== nullsが本当に嫌いなら、これは解決策です。

1
charleyc

ネストされたnullチェックを回避するための、私のお気に入りの単純なtry/catch ...

try {
    if(order.getFulfillmentGroups().get(0).getAddress().getPostalCode() != null) {
        // your code
    } 
} catch(NullPointerException|IndexOutOfBoundsException e) {}
0
Keith Klingeman

私は同じことを探していました(私のコンテキスト:自動的に作成された多数のJAXBクラス、そしてどういうわけか.getFoo().getBar()...のこれらの長いデイジーチェーンがあります。常に、時々、中間はnullを返し、NPEを引き起こします。

しばらく前に私がいじり始めたものは、反射に基づいています。この見た目をより効率的にすることができると思います(たとえば、リフレクションをキャッシュし、さらに._allなどの「マジック」メソッドを定義して、コレクションのすべての要素を自動的に反復する場合真ん中はコレクションを返します)。きれいではありませんが、おそらくもっと良いものがすでにあるか誰かが教えてくれるでしょう:

/**
 * Using {@link Java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion)
 * to the array of Objects x.
 * 
 * <p>For example, imagine that you'd like to express:
 * 
 * <pre><code>
 * Fubar[] out = new Fubar[x.length];
 * for (int i=0; {@code i<x.length}; i++) {
 *   out[i] = x[i].getFoo().getBar().getFubar();
 * }
 * </code></pre>
 * 
 * Unfortunately, the correct code that checks for nulls at every level of the
 * daisy-chain becomes a bit convoluted.
 * 
 * <p>So instead, this method does it all (checks included) in one call:
 * <pre><code>
 * Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar");
 * </code></pre>
 * 
 * <p>The cost, of course, is that it uses Reflection, which is slower than
 * direct calls to the methods.
 * @param type the type of the expected result
 * @param x the array of Objects
 * @param methods the methods to apply
 * @return
 */
@SuppressWarnings("unchecked")
public static <T> T[] apply(T[] type, Object[] x, String...methods) {
    int n = x.length;
    try {
        for (String methodName : methods) {
            Object[] out = new Object[n];
            for (int i=0; i<n; i++) {
                Object o = x[i];
                if (o != null) {
                    Method method = o.getClass().getMethod(methodName);
                    Object sub = method.invoke(o);
                    out[i] = sub;
                }
            }
            x = out;
        }
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n);
    for (int i=0; i<n; i++) {
            result[i] = (T)x[i];
    }
            return result;
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new RuntimeException(e);
    }
}
0
Pierre D