NullチェックチェーンとNullPointerExceptionのキャッチ


118

Webサービスが巨大なXMLを返すので、それの深くネストされたフィールドにアクセスする必要があります。例えば:

return wsObject.getFoo().getBar().getBaz().getInt()

問題はそれです getFoo()getBar()getBaz()すべて返すことがありnull

しかし、私がチェックした場合 nullすべてのケースでと、コードが非常に冗長になり、読みにくくなります。また、一部のフィールドのチェックが抜ける場合があります。

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

書いてもいいですか

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

それともアンチパターンと見なされますか?


29
すでにコードのにおいがしているnullので、チェックはそれほど気にしませんwsObject.getFoo().getBar().getBaz().getInt()「デメテルの法則」とは何かを読み、それに応じてコードをリファクタリングすることをお勧めします。その後、nullチェックの問題もなくなります。そして、の使用について考えますOptional
トム・

9
XPathを使用して、評価に任せるのはどうですか?
Joop Eggen

15
そのコードはおそらくによって生成されwsdl2java、デメテルの法則を尊重しません。
エイドリアンコックス

回答:


143

キャッチNullPointerExceptionはほとんどどこでも発生する可能性があるため、実行するのは本当に問題の多いことです。バグから1つ取得し、それを偶然見つけて、すべてが正常であるかのように続行することは非常に簡単であり、それによって実際の問題を隠すことができます。対処するのはとても難しいので、完全に回避するのが最善です。(たとえば、nullの自動ボックス化解除について考えてくださいInteger。)

Optional代わりにクラスを使用することをお勧めします。これは、存在する値または存在しない値を処理する場合に最適なアプローチです。

それを使用して、次のようなコードを書くことができます:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

なぜオプションですか?

Optionalの代わりにs を使用するnull存在しない可能性のある値すると、その事実が読者に非常にわかりやすくなり、型システムによって、誤ってそれを忘れないようになります。

あなたも同じように、より便利な値を操作するためのメソッドへのアクセスを取得mapし、orElse


欠席は有効ですか、それともエラーですか?

ただし、中間メソッドがnullを返すことが有効な結果であるか、それがエラーの兆候であるかについても検討してください。常にエラーである場合は、特別な値を返すよりも、または中間メソッド自体が例外をスローするよりも、おそらく例外をスローする方が適切です。


多分もっとオプション?

一方、中間メソッドに存在しない値が有効である場合、多分あなたはに切り替えることができます Optionalそれら sにこともできますか?

その後、次のように使用できます。

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

なぜオプションではないのですか?

私が使用しないと考えることができる唯一の理由Optionalは、これがコードの本当にパフォーマンス上重要な部分にあるかどうか、そしてガベージコレクションのオーバーヘッドが問題であることが判明したかどうかです。これはOptional、コードが実行されるたびにいくつかのオブジェクトが割り当てられ、VM それらを離れて最適化できない可能性があるためです。その場合、元のifテストの方が良いかもしれません。


とても良い答えです。他に考えられる障害状態があり、それらを区別する必要がある場合は、のTry代わりに使用できることに注意してくださいOptionalTryJava API にはありませんが、javaslang.iogithub.com/ bradleyscollins / try4jfunctionaljava.org、またはgithub.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBarなどは短くなります。
Boris the Spider

1
@BoristheSpider:たぶん少し。しかし、クラス名がはるかに長いことが多いため、通常はメソッド参照よりもラムダを好み、ラムダは少し読みやすいと感じます。
Lii

6
@Liiは十分に公平ですが、ラムダはより複雑なコンパイル時間構成を必要とする場合があるため、メソッド参照は少し高速になる可能性があることに注意してください。ラムダはstaticメソッドの生成を必要とし、非常に小さなペナルティが発生します。
Boris the Spider

1
@Lii実際には、メソッド参照が少し長くても、メソッド参照がより簡潔で説明的であることがわかります。
shmosel

14

検討することをお勧めしますObjects.requireNonNull(T obj, String message)。次のように、例外ごとに詳細なメッセージを含むチェーンを構築できます。

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

のような特別な戻り値を使用しないことをお勧めします-1。これはJavaスタイルではありません。Javaは、C言語から来たこの昔ながらの方法を避けるために、例外のメカニズムを設計しました。

投げるNullPointerExceptionことも最良の選択肢ではありません。独自の例外を提供する(ユーザーによって処理されることを保証するためにチェックするか、簡単な方法で処理するためにオフにする)か、使用しているXMLパーサーから特定の例外を使用できます。


1
Objects.requireNonNull最終的にスローしNullPointerExceptionます。だから、これは状況を変えませんreturn wsObject.getFoo().getBar().getBaz().getInt()
Arka Ghosh

1
@ArkaGhosh、またそれはifOPが示したように多くのsを避けます
Andrew Tobilko

4
これが唯一の健全な解決策です。他のすべての人は、コードのにおいであるフロー制御の例外を使用することを勧めます。余談ですが、私はOPによって実行されるメソッドチェーンもにおいだと考えています。彼が3つのローカル変数を使用する場合、対応するifの状況ははるかに明確になります。また、問題は単にNPEを回避することよりも深いと思います。ゲッターがnullを返すことができる理由をOPは自問する必要があります。nullとはどういう意味ですか?たぶん、いくつかのnullオブジェクトが良いでしょうか?または、意味のある例外を伴う1つのゲッターでクラッシュしますか?基本的にすべてはフロー制御の例外よりも優れています。
マリウスK.

1
例外を使用して有効な戻り値がないことを示す無条件のアドバイスはあまり良くありません。例外は、呼び出し側が回復するのが難しく、プログラムの他の部分のtry-catch-statementでより適切に処理される方法でメソッドが失敗した場合に役立ちます。単に戻り値がないことを通知するには、Optionalクラスを使用するか、nullableを返すほうがよいInteger
Lii

6

クラス構造が確かに私たちの制御の外にあると仮定すると、ケースのように思われますが、パフォーマンスに大きな懸念がない限り、質問で提案されているようにNPEを捕捉することは確かに合理的な解決策だと思います。小さな改善の1つは、混乱を避けるためにスロー/キャッチロジックをラップすることです。

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

今、あなたは単に行うことができます:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");問題になる可能性のあるコンパイル時のエラーは発生しません。
フィリップジョセフィ

5

トムがコメントですでに指摘したように、

次の声明は、デメテル法則に違反しています

wsObject.getFoo().getBar().getBaz().getInt()

あなたが欲しいのはint、あなたがそれを得ることができることですFooデメテルの法則によると、見知らぬ人には決して話さないでください。あなたのケースでは、実際の実装をFooand のフードの下に隠すことができますBar

さて、あなたは内のメソッドを作成することができますFooフェッチするintからとBaz。最終的にFooBar、に直接BarアクセスするIntことなくアクセスできます。したがって、nullチェックはおそらく異なるクラスに分割され、必要な属性のみがクラス間で共有されます。BazFoo


4
WsObjectはおそらくデータ構造にすぎないため、デメテルの法則に違反している場合は議論の余地があります。こちらをご覧ください:stackoverflow.com/a/26021695/1528880
DerM

2
@DerMはい、それは可能ですが、OPには既にXMLファイルを解析するものがあるため、必要なタグに適したモデルクラスを作成して、解析ライブラリがそれらをマップできるようにすることもできます。次に、これらのモデルクラスには、null独自のサブタグをチェックするためのロジックが含まれます。
トム

4

私の答えは@jankiとほとんど同じ行にありますが、コードスニペットを次のように少し変更したいと思います。

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

wsObjectオブジェクトがnullになる可能性がある場合は、nullチェックを追加することもできます。


4

一部のメソッドは「戻る可能性があります」nullと言いますが、どのような状況で返すかは言いませんnull。あなたはあなたが捕まえると言いますNullPointerExceptionがそれが、なぜあなたがそれを捕まえるかは言いません この情報の欠如は、例外が何であるのか、なぜ例外が代替より優れているのかを明確に理解していないことを示唆しています。

アクションを実行することを意図したクラスメソッドを考えますが、制御できない状況のため、メソッドはアクションの実行を保証できませ(実際にはJavaのすべてのメソッドに当てはまります)。そのメソッドを呼び出すと戻ります。そのメソッドを呼び出すコードは、それが成功したかどうかを知る必要があります。どうやってそれを知ることができますか?成功と失敗の2つの可能性に対処するために、それをどのように構成できますか?

例外を使用して、事後条件として成功するメソッドを作成できます。メソッドが戻る場合、それは成功しています。例外をスローする場合は、失敗しています。これは明確にするための大きな勝利です。通常の成功ケースを明確に処理するコードを記述し、すべてのエラー処理コードをcatch句に移動できます。メソッドが失敗した方法または理由の詳細は呼び出し元にとって重要ではないため、同じcatch節をいくつかのタイプの失敗の処理に使用できることがよくあります。そして、多くの場合、メソッドがキャッチ例外に必要がないことが起こるすべてで、ちょうど彼らがに伝播させることができ、その呼び出し側。プログラムのバグによる例外は、後者のクラスにあります。バグがある場合に適切に対応できるメソッドはほとんどありません。

したがって、を返すメソッドnull

  • null値は、あなたのコードにバグがあることを示して?そうであれば、例外をまったくキャッチするべきではありません。そして、あなたのコードは自分自身を推測しようとしてはいけません。うまくいくという前提で、明確で簡潔なものを書いてください。メソッド呼び出しのチェーンは明確で簡潔ですか?次に、それらを使用します。
  • null値は、あなたのプログラムへの無効な入力を示していますか?その場合NullPointerException、従来はバグを示すために予約されているため、スローするのに適切な例外ではありません。IllegalArgumentExceptionチェックされていない例外IOExceptionが必要な場合)または(チェックされた例外が必要な場合)から派生したカスタム例外をスローする必要があります。無効な入力がある場合、プログラムは詳細な構文エラーメッセージを提供する必要がありますか?その場合は、各メソッドのnull戻り値を確認し、適切な診断例外をスローすることだけが可能です。プログラムが詳細な診断を提供する必要がない場合は、メソッド呼び出しを一緒にチェーンし、何かNullPointerExceptionをキャッチしてからカスタム例外をスローするのが最も明確で簡潔です。

回答の1つは、連鎖メソッド呼び出しがデメテル法則に違反しているため、不適切であると主張しています。その主張は間違っています。

  • プログラムの設計に関しては、何が良いか、何が悪いかについて、絶対的なルールはありません。ヒューリスティックのみがあります。ほとんどすべての時間で正しいルールです。プログラミングのスキルの一部は、これらの種類のルールを破ることがいつ許可されるかを知ることです。したがって、「これはルールXに違反している」という簡潔な主張は、実際にはまったく答えにはなりません。これはルールがすべき状況の1つです破るがですか?
  • デメテル法則は、実際にはAPIまたはクラスインターフェイスの設計に関するルールです。クラスを設計するときは、抽象化の階層があると便利です。言語プリミティブを使用して直接操作を実行し、言語プリミティブよりも高いレベルの抽象化でオブジェクトを表す低レベルのクラスがあります。低レベルのクラスに委任する中レベルのクラスがあり、低レベルのクラスよりも高いレベルで操作と表現を実装しています。中レベルのクラスに委任する高レベルのクラスがあり、さらに高いレベルの操作と抽象化を実装しています。(ここでは、3つの抽象化レベルについてのみ説明しましたが、さらに多くのことが可能です)。これにより、コードは各レベルで適切な抽象化の観点から自分自身を表現できるため、複雑さを隠すことができます。の根拠デメテル法則一連のメソッド呼び出しがある場合、高レベルのクラスが中レベルのクラスを介して低レベルの詳細を直接処理することを示唆しているため、中レベルのクラスは中レベルの抽象操作を提供していません。高レベルのクラスが必要とすること。しかし、それはあなたがここにいる状況ではないようです:メソッド呼び出しのチェーンでクラスを設計しなかった、それらはいくつかの自動生成されたXMLシリアル化コードの結果です(そうですか?)、呼び出しのチェーンは降順ではありません逆シリアル化されたXMLはすべて抽象化階層の同じレベルにあるため(抽象化階層を通じて)(そうですか?)

3

読みやすくするために、次のような複数の変数を使用することをお勧めします

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();

これは私の意見では読みにくいです。これは、メソッドの実際のロジックとはまったく関係のない一連のnullチェックロジックでメソッドをリッターします。
Developer102938

2

キャッチしませんNullPointerException。あなたはそれがどこから来ているのかわからない(私はそれがあなたのケースではありそうもない多分何かがそれを投げた)ことを知っている、そしてそれは遅い。指定したフィールドにアクセスする必要があり、そのためには他のすべてのフィールドがnullでない必要があります。これは、すべてのフィールドをチェックするのに最適な正当な理由です。私はおそらくそれを1つにチェックインし、読みやすいメソッドを作成します。他の人がすでに-1を返すことを指摘しているように、非常に古い学校ですが、理由があるかどうかはわかりません(たとえば、別のシステムと通信している)。

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

編集:WsObjectはおそらくデータ構造のみであるため、デメテルの法則に違反している場合は議論の余地がありますhttps://stackoverflow.com/a/26021695/1528880を確認してください)。


2

コードをリファクタリングする必要がなく、Java 8を使用できる場合は、メソッド参照を使用できます。

最初に簡単なデモ(静的内部クラスを除く)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

出力

241
4

ヌル
2

インターフェースGetterは単なる機能的なインターフェースであり、同等のものを使用できます。
GetterResultクラス、わかりやすくするために削除されたアクセサー、ゲッターチェーンの結果(ある場合)、または最後に呼び出されたゲッターのインデックスを保持します。

このメソッドgetterChainは、自動で(または必要に応じて手動で)生成できる単純な定型コードです。
繰り返しブロックが自明になるようにコードを構成しました。


まだ1つのオーバーロードを定義する必要があるため、これは完全なソリューションではありません。 getterChainゲッターの数ごとに1。

私は代わりにコードをリファクタリングしますが、それができず、長いゲッターチェーンを使用している場合は、2から、たとえば10ゲッターのオーバーロードを使用してクラスを構築することを検討します。


2

他の人が言ったように、デメテルの法則を尊重することは間違いなく解決策の一部です。別の部分は、可能な限り、これらのチェーンされたメソッドを変更して、戻れないようにすることnullです。null代わりに、空String、空Collection、またはその他のダミーオブジェクトを返すことで戻りを回避できます。これは、呼び出し側が行うことを意味するか、または実行することを実行しnullます。


2

エラーの意味に焦点を当てた回答を追加したいと思います。null例外自体は、意味のない完全なエラーを提供しません。だから私はそれらを直接扱うことを避けることをお勧めします。

コードに問題が発生する可能性のあるケースは数千あります:データベースに接続できない、IO例外、ネットワークエラー...それらを1つずつ処理する場合(ここでのnullチェックのように)、それは面倒です。

コードで:

wsObject.getFoo().getBar().getBaz().getInt();

どのフィールドがnullであるかを知っていても、何が問題なのかはわかりません。多分バーは空ですが、それは予想されますか?それともデータエラーですか?コードを読んだ人について考える

xenterosの回答と同様に、カスタムのチェックされていない例外を使用することをお勧めします。たとえば、この状況では、Fooをnull(有効なデータ)にすることができますが、BarとBazをnull(無効なデータ)にすることはできません。

コードは書き直すことができます:

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}

チェックされていない例外は、プログラマーがAPIを誤用していることを示しています。「データベースに接続できない、IO例外、ネットワークエラー」などの外部の問題は、チェックされた例外によって示されます。
Kevin Krumwiede

それは本当に呼び出し側の必要性に依存します。エラーの処理を強制するため、例外ヘルプを確認しました。ただし、それ以外の場合は不要であり、コードを汚染する可能性があります。たとえば、データレイヤーにIOExceptionがある場合、それをプレゼンテーションレイヤーにスローしますか?つまり、例外をキャッチして、すべての呼び出し元で再スローする必要があります。グローバルフィルターがそれをキャッチしてユーザーにメッセージを表示するまで、IOExceptionを関連するメッセージでカスタムBusinessExceptionでラップし、スタックトレースをポップさせたいと思います。
ホアンロング

呼び出し側は、チェックされた例外をキャッチして再スローする必要はなく、スローされるように宣言するだけです。
Kevin Krumwiede

@KevinKrumwiede:正解です。スローする例外を宣言するだけです。ただし、まだ宣言する必要があります。編集:2度目を見ると、チェックされた例外とチェックされていない例外の使用法についてかなりの議論があります(例:programmers.stackexchange.com/questions/121328/…)。
ホアンロング

2

NullPointerException は実行時の例外であるため、一般的に言えば、それをキャッチするのではなく、回避することをお勧めします。

メソッドを呼び出したい場所で例外をキャッチする必要があります(または、スタックを伝播します)。それにもかかわらず、あなたの場合、値-1でその結果を使い続けることができ、nullである可能性のある「ピース」を使用していないために伝播しないことが確実である場合、それは私には正しいようです捕まえろ

編集:

@xenterosからの後者の回答に同意しInvalidXMLExceptionます。たとえば、それを呼び出すことができる-1を返す代わりに、独自の例外を起動する方が良いでしょう。


3
「それを捕まえても、コードの他の部分に伝播する可能性がある」とはどういう意味ですか?
ハルク

nullがこの文に含まれている場合wsObject.getFoo()そして、コードの後半でそのクエリを再度実行するか、またはwsObject.getFoo()。getBar()(たとえば)を使用すると、再びNullPointerExceptionが発生します。
SCouto 2016年

これは、「メソッドを呼び出したい場所ではどこでも例外をキャッチする必要がある(またはスタックを上に伝播する)」という珍しい表現です。私が正しく理解できれば。私はそれに同意します(それが問題である可能性があります)、私は言葉遣いが混乱していることに気づきます。
ハルク

私はそれを修正します。申し訳ありませんが、私の母国語は英語ではないので、これは時々起こるかもしれません:)ありがとう
SCouto

2

昨日からこの投稿をフォローしています。

私は言っているコメントをコメント/投票しています、NPEを捕まえることは悪いです。これが私がそうしている理由です。

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

出力

3.216

0.002

ここに明確な勝者がいます。チェックがあるかどうかを確認することは、例外をキャッチするよりもはるかに安価です。私はそのJava-8のやり方を見てきました。現在のアプリケーションの70%がまだJava-7で実行されていることを考慮して、この答えを追加します。

結論ミッションクリティカルなアプリケーションでは、NPEの処理にコストがかかります。


最悪の場合、 100万回のリクエスト 3秒余分に測定できますが、「ミッションクリティカルなアプリケーション」であっても、それが取引ブレーカーになることはめったにありません。リクエストに3.2マイクロ秒を追加することが重要なシステムがあります。そのようなシステムがある場合は、例外について慎重に検討してください。しかし、元の質問に従って、Webサービスを呼び出してその出力をデシリアライズすると、おそらくそれよりもはるかに時間がかかり、例外処理のパフォーマンスを心配することは重要ではありません。
Jeroen Mostert

@JeroenMostert:チェックあたり3秒/ 100万。したがって、チェックの数によってコストが増加します
NewUser

そうだね。それでも、「プロファイルファースト」の場合を考えます。リクエストが完全なミリ秒を追加する前に、1つのリクエストで300を超えるチェックが必要になります。デザインの考慮は、それよりはるかに早く私の魂に重荷をかけるでしょう。
Jeroen Mostert

@JeroenMostert::)同意しました!結果をプログラマーに任せて、彼らに電話をかけてもらいたいです!
NewUser

1

効率が問題である場合、「キャッチ」オプションを検討する必要があります。( 'SCouto'で言及されているように)伝播するために 'catch'を使用できない場合は、ローカル変数を使用して、メソッドgetFoo()getBar()およびへの複数の呼び出しを回避しますgetBaz()


1

独自の例外を作成することを検討する価値があります。MyOperationFailedExceptionとしましょう。代わりに値を返すことができます。結果は同じになります-関数は終了しますが、Javaアンチパターンであるハードコードされた値-1は返されません。Javaでは例外を使用します。

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

編集:

コメントでの議論によると、私は以前の考えに何かを加えさせてください。このコードには2つの可能性があります。1つはnullを受け入れること、もう1つはエラーであることです。

それがエラーで発生した場合、ブレークポイントが十分でないときにデバッグ目的で他の構造を使用してコードをデバッグできます。

許容できる場合は、このヌルがどこに現れたかは気にしません。もしそうなら、あなたは間違いなくそれらのリクエストをチェーンすべきではありません。


2
例外を抑制するのは悪い考えではありませんか?リアルタイムで、例外の痕跡を失うと、地獄が何が起こっているのかを見つけるための底の真の痛み!私は常にチェーンを使用しないことをお勧めします。私が目にする2番目の問題は、次のとおりです。このコードは、現時点では権限を付与できません。どちらの結果がnullでしたか。
NewUser

いいえ、例外には、例外がスローされた場所を確実に示すメッセージを含めることができます。連鎖は最善の解決策ではないことに同意します:)
xenteros

3
いいえ、それは行番号についてだけ言うでしょう。したがって、チェーン内の呼び出しのいずれかが例外につながる可能性があります。
NewUser

「エラーで発生した場合は、コードをデバッグできます」-本番環境ではありません。何が起こって何が失敗したのかを理解しようとするよりも、ログがすべての場合に何が失敗したのかを知りたいのです。そのアドバイス(およびそのコード)を使用すると、4つのうちの1つがnullであったが、どれが原因でなかったかがわかるだけです。
VLAZ

1

あなたが持っている方法は長いですが、非常に読みやすいです。私があなたのコードベースにやって来る新しい開発者だったら、あなたがやっていることをかなり素早く見ることができました。他のほとんどの答え(例外の捕捉を含む)は、物事を読みやすくするようには見えません。

生成されたソースを制御できない可能性があり、深くネストされたいくつかのフィールドに本当にアクセスする必要があると仮定すると、深くネストされた各アクセスをメソッドでラップすることをお勧めします。

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

これらのメソッドを多数記述している場合や、これらのパブリック静的メソッドを作成したい場合は、別のオブジェクトモデルを作成し、必要なフィールドだけを入れてネストし、Webから変換します。オブジェクトモデルをオブジェクトモデルにサービスします。

リモートWebサービスと通信しているときは、「リモートドメイン」と「アプリケーションドメイン」があり、その2つを切り替えるのが非常に一般的です。リモートドメインは多くの場合、Webプロトコルによって制限されます(たとえば、純粋なRESTfulサービスでヘルパーメソッドを送受信できないため、複数のAPI呼び出しを回避するために深くネストされたオブジェクトモデルが一般的です)。あなたのクライアント。

例えば:

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}

1
return wsObject.getFooBarBazInt();

デメテルの法則を適用することにより、

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}

0

他のすべてとは異なると思われる答えを与える。

私はあなたがチェックすることをお勧めNULLしてif秒。

理由:

プログラムがクラッシュする可能性を1つ残してはなりません。NullPointerはシステムによって生成されます。システムで生成された例外の動作は予測できません。自分でプログラムを処理する方法がすでにある場合は、プログラムをシステムに任せないでください。そして、特別な安全のために例外処理メカニズムを配置します。

コードを読みやすくするには、次の条件を確認してください。

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

編集:

ここwsObject.getFoo()wsObject.getFoo().getBar()、これらの値wsObject.getFoo().getBar().getBaz()をいくつかの変数に格納する必要があります。その関数の戻りの型がわからないので、私はそれをしていません。

任意の提案をいただければ幸いです。


getFoo()は非常に時間のかかる操作だと思いましたか?戻り値は変数に格納する必要がありますが、これはメモリの浪費です。この方法は、Cプログラミングに最適です。
xenteros 2016年

ただし、プログラムが1ミリ秒遅れてからプログラムがクラッシュする@xenteros .. !!
Janki Gadhiya

getFoo()は、別の大陸にあるサーバーから値を取得する場合があります。それはいつでも続くことができます:mins / hours ...
xenteros

wsObjectWebサービスから返された値が含まれます。サービスはすでに呼び出されており、Webサービス応答としてwsObject長いXMLデータを取得します。だから、のようなものは何もない、別の大陸にあるサーバーがためgetFoo()だけの要素になっているgetterメソッドではないWebサービスの呼び出し..!@xenteros
Janki Gadhiya

1
ゲッターの名前から、Foo、Bar、Bazオブジェクトを返すと思います:Pまた、前述の二重の安全性を回答から削除することも検討してください。私はそれがコードの汚染を除けば本当の価値を提供するとは思わない。健全なローカル変数とnullチェックにより、コードの正確さを保証するのに十分以上のことができました。例外が発生する可能性がある場合は、例外として処理する必要があります。
マリウスK.

0

Snagオブジェクトのツリー内を移動するためのパスを定義できるというクラスを作成しました。以下はその使用例です。

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

つまり、インスタンスENGINE_NAMECar?.getEngine()?.getName()渡されたインスタンスを効果的に呼び出しnull、参照が返された場合は返されnullます。

final String name =  ENGINE_NAME.get(firstCar);

これはMavenで公開されていませんが、これが便利だと思った人はここにいます(もちろん保証はありません!)

それは少し基本的ですが、それは仕事をするようです。安全なナビゲーションをサポートするJavaやその他のJVM言語の最新バージョンやでは、明らかに時代遅れになっていますOptional

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.