自然に発生させるのではなく、明示的にNullPointerExceptionをスローするのはなぜですか?


182

JDKソースコードを読むとき、作成者がパラメーターがnullかどうかを確認し、新しいNullPointerException()を手動でスローすることがよくあります。なぜ彼らはそれをするのですか?メソッドを呼び出すと新しいNullPointerException()がスローされるので、そうする必要はないと思います。(たとえば、HashMapのソースコードは次のとおりです:)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}

32
コーディングの重要なポイントが意図されている
Scary Wombat

19
これはあなたの最初の質問にとても良い質問です!いくつかのマイナーな編集を行いました。よろしくお願いします。また、通常はそのようなことはSOの質問の一部ではないので、お礼と最初の質問であるというメモも削除しました。
デビッドコンラッド

11
私はC#ですが、ArgumentNullExceptionそのような場合に(でなくNullReferenceException)発生させる規則です-なぜNullPointerExceptionここで明示的に(別のものではなく)発生させるのかについては、本当に良い質問です。
EJoshuaS-モニカを2017年

21
@EJoshuaS スローするか、null引数にするかは、古い議論です。JDK規約は後者です。IllegalArgumentExceptionNullPointerException
shmosel

33
REALの問題は、エラーをスローし、このエラーにつながるすべての情報を破棄することです。これ実際のソースコードのようです。単純な流血の文字列メッセージすらありません。悲しい。
マーティン・バ2017年

回答:


254

頭に浮かぶ理由はいくつかあり、いくつかは密接に関連しています。

フェイルファスト:失敗する場合は、後で失敗するよりも早く失敗することが最善です。これにより、問題を発生源の近くで捉えることができ、問題の特定と回復が容易になります。また、失敗するはずのコードでCPUサイクルを浪費することも回避します。

意図:例外をスローすることで、エラーが意図的に存在し、作成者が結果を認識していることが保守担当者に明確に示されます。

一貫性:エラーが自然に発生することが許可されている場合、すべてのシナリオで発生するわけではありません。たとえば、マッピングが見つからない場合はremappingFunction使用されず、例外はスローされません。入力を事前に検証することで、動作がより確定的になり、ドキュメントがより明確になります

安定性:コードは時間とともに進化します。例外が発生したコードは、少しのリファクタリングの後、自然にそうするのをやめるか、さまざまな状況下でそうするかもしれません。明示的にスローすることで、動作が誤って変更される可能性が低くなります。


14
また、このようにして、例外がスローされる場所は、チェックされる1つの変数に関連付けられます。これがない場合、例外は複数の変数の1つがnullであることが原因である可能性があります。
イェンスシャウダー

45
もう1つは、NPEが自然に発生するのを待つ場合、中間のコードの一部がすでに副作用によってプログラムの状態を変更している可能性があります。
トーマス

6
このスニペットはそれを行いませんが、new NullPointerException(message)コンストラクタを使用してnullが何であるかを明確にすることができます。ソースコードにアクセスできない人に適しています。Objects.requireNonNull(object, message)ユーティリティメソッドを使用して、これをJDK 8のワンライナーにさえしました。
ロビン

3
FAILUREはFAULTに近いはずです。「Fail Fast」は経験則以上のものです。いつこの動作を望まないのですか?その他の動作は、エラーを非表示にしていることを意味します。「FAULTS」と「FAILURES」があります。FAILUREは、このプログラムがNULLポインターをダイジェストしてクラッシュする場合です。しかし、そのコード行は、FAULTが存在する場所ではありません。NULLはどこかから来た-メソッドの引数。誰がその議論をパスしましたか?ローカル変数を参照するコード行から。それはどこにありましたか?それは最悪です。悪い値が保存されるのを見るのは誰の責任でしょうか?あなたのプログラムはその時クラッシュしたはずです。
ノアスプリアー

4
@トーマス良い点。シュモセル:トーマスのポイントはフェイルファストポイントに含まれるかもしれませんが、それは一種の埋もれています。これは、独自の名前をもつ十分に重要な概念です。失敗の原子性です。Bloch、Effective Java、Item 46を参照してください。フェイルファストよりもセマンティクスが強力です。別のポイントでそれを呼び出すことをお勧めします。ところで、全体的に素晴らしい答えです。+1
スチュアートマーク

40

これは、明確性、一貫性、および余分な不要な作業が実行されないようにするためです。

メソッドの先頭にガード句がない場合はどうなるか考えてみてください。それは、常に呼ぶだろうhash(key)getNode(hash, key)するときにもnullために渡されていたremappingFunctionNPEがスローされる前に。

さらに悪いことに、if条件がfalse次にある場合、else分岐を使用します。分岐はまったく使用されませんremappingFunction。つまり、a nullが渡されたときにメソッドが常にNPEをスローするとは限りません。かどうかは、マップの状態によって異なります。

どちらのシナリオも悪いです。if nullremappingFunctionメソッドの有効な値でない場合は、呼び出し時のオブジェクトの内部状態に関係なく、常に例外をスローする必要があり、スローしようとしているという意味で無意味な不要な処理を行わずに例外をスローする必要があります。最後に、ソースコードをレビューする人が誰でもそれができることをすぐにわかるように、ガードをすぐ前に置くことは、クリーンで明確なコードの良い原則です。

例外がコードのすべてのブランチによって現在スローされている場合でも、コードの将来のリビジョンでそれが変更される可能性があります。最初にチェックを実行すると、確実に実行されます。


25

@shmoselの優れた答えによってリストされた理由に加えて...

パフォーマンス:(一部のJVMでは)JVMに実行させるのではなく、NPEを明示的にスローすることでパフォーマンスが向上する可能性があります。

これは、JavaインタープリターとJITコンパイラーがNULLポインターの逆参照を検出するために取る戦略に依存します。1つの戦略は、nullをテストせず、代わりに、命令がアドレス0にアクセスしようとしたときに発生するSIGSEGVをトラップすることです。これは、参照が常に有効である場合の最速のアプローチですが、NPEの場合はコストかかります。

nullコード内での明示的なテストにより、NPEが頻繁に発生するシナリオでSIGSEGVのパフォーマンスヒットが回避されます。

(これは現代のJVMで価値のあるマイクロ最適化になるとは思えませんが、過去にあったかもしれません。)


互換性:例外にメッセージがない理由として考えられるのは、JVM自体によってスローされるNPEとの互換性です。準拠したJava実装では、JVMによってスローされたNPEにnullメッセージがあります。(Android Javaは異なります。)


20

他の人々が指摘したこととは別に、ここでは慣習の役割に​​注目する価値があります。たとえばC#では、このような場合に例外を明示的に発生させるという同じ規則がありますがArgumentNullException、これは具体的にはであり、やや具体的です。(C#の慣例は、NullReferenceException 常にある種のバグを表すというものです。非常に簡単に言うと、製品コード発生することは決してありません。当然のことですが、ArgumentNullException通常は発生しますが、「you not notライブラリを正しく使用する方法を理解してください」という種類のバグ)。

つまり、基本的には、C#NullReferenceExceptionはプログラムが実際に使用しようとしたArgumentNullExceptionことを意味しますが、値が間違っていることを認識し、それを使用しようとすることさえありませんでした。ArgumentNullException問題のメソッドにはまだ副作用がない(メソッドの前提条件に失敗したため)ため、影響は実際には(状況によって)異なる場合があります。

ちなみに、ArgumentNullExceptionまたはのようなものを発生させている場合IllegalArgumentException、それはチェックを行うポイントの一部です。「通常」取得するのとは異なる例外が必要です。

どちらの方法でも、明示的に例外を発生させることで、メソッドの前提条件と予期される引数を明示的にすることで、コードの読み取り、使用、保守が容易になります。を明示的にチェックnullしなかった場合、誰もnull引数を渡さないと考えていたのか、それとも例外をスローするためにそれをカウントしていたのか、それともチェックするのを忘れたのかはわかりません。


4
真ん中の段落の+1。私は問題のコードが「新しいIllegalArgumentException( "remappingFunctionをnullにすることはできません");をスローする必要がある」と主張します。そうすれば、何が悪いのかすぐにわかります。表示されているNPEは少しあいまいです。
クリスパーカー

1
@ChrisParker以前は同じ意見でしたが、NullPointerExceptionは、nullを逆参照しようとする試みに対する実行時の応答であることに加えて、null以外の引数を期待するメソッドに渡されるnull引数を示すことを意図していることがわかりました。javadocから:「アプリケーションはこのクラスのインスタンスをスローして、nullオブジェクトの他の不正な使用を示す必要があります。」私はそれに夢中ではありませんが、それは意図されたデザインのようです。
VGR

1
私は同意します、@ ChrisParker-その例外はより具体的だと思います(コードは値を使用して何も実行しようとしたことがないため、使用するべきではないことをすぐに認識しました)。この場合、C#の規則が好きです。C#の規則はNullReferenceException(と同等NullPointerException)は、コードが実際にそれを使用しようとしたことを意味します(これは常にバグであり、製品コードでは決して発生しないはずです)対「私は引数が間違っていることを知っているので、それを使ってみてください。」またArgumentException、それはあります(これは、他の理由で引数が間違っていたことを意味します)。
EJoshuaS-モニカを2017年

2
これだけ言っておきますが、説明したように常にIllegalArgumentExceptionをスローします。私は、慣習が愚かであると感じたとき、いつも快適な大騒ぎを感じます。
クリスパーカー

1
@PieterGeerkens-そうです、NullPointerExceptionの35行目はIllegalArgumentException( "Function ca n't be null")行35よりもはるかに優れているためです。
クリスパーカー

12

これは、後でマップを使用しているときにエラーが発生した理由が理解できなくなるのではなく、エラーが発生するとすぐに例外が発生するためです。


9

一見不安定なエラー状態が明確なコントラクト違反に変わります。関数には、正常に動作するためのいくつかの前提条件があるため、事前にチェックして、それらが満たされるようにします。

その結果computeIfPresent()、例外が発生したときにデバッグする必要がなくなります。例外が前提条件チェックによるものであることがわかると、不正な引数を指定して関数を呼び出したことがわかります。チェックがなかった場合はcomputeIfPresent()、例外がスローされる原因となるバグが含まれている可能性を除外する必要があります。

ジェネリックをスローするNullPointerExceptionことは、それ自体が契約違反を示すものではないため、明らかに悪い選択です。IllegalArgumentExceptionより良い選択でしょう。


サイドノート:
Javaがこれを許可するかどうかはわかりませんが(私は疑問です)、C / C ++プログラマーassert()はこの場合にを使用します。これは、デバッグに非常に優れています。提供された場合、プログラムをすぐにクラッシュし、可能な限りハードクラッシュするように指示します。条件はfalseと評価されます。だから、あなたが走った場合

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
    assert(me);
    assert(someFunction);

    ...
}

デバッガーの下で何かがNULLいずれかの引数に渡された場合、プログラムは行で停止し、どの引数がNULLであるかを示します。自由に呼び出しスタック全体のすべてのローカル変数を調べることができます。


1
assert something != null;ただし-assertions、アプリを実行するときにフラグが必要です。場合は-assertionsフラグがない、assertキーワードはAssertionExceptionスローされません
ゾーイ

私が同意するのは、ここでC#規則を使用する理由です。null参照、無効な引数、null引数はすべて、通常、ある種のバグを意味しますが、異なる種類のバグを意味します。「null参照を使用しようとしている」ことは、「ライブラリを誤用している」こととは大きく異なります。
EJoshuaS-モニカの復活2017年

7

自然に起こらない可能性があるからです。次のようなコードを見てみましょう。

bool isUserAMoron(User user) {
    Connection c = UnstableDatabase.getConnection();
    if (user.name == "Moron") { 
      // In this case we don't need to connect to DB
      return true;
    } else {
      return c.makeMoronishCheck(user.id);
    }
}

(もちろん、このサンプルにはコードの品質に関する多くの問題があります。完璧なサンプルを想像するのが面倒です)

c実際には使用されNullPointerExceptionず、c == null可能な場合でもスローされない状況。

より複雑な状況では、そのようなケースを突き止めることは非常に簡単ではなくなります。これは、一般的なチェックのようif (c == null) throw new NullPointerException()に優れている理由です。


間違いなく、本当に必要でないときにデータベース接続なしで機能するコードは良いことであり、データベースに接続して失敗するかどうかを確認するだけのコードは、通常、かなり煩わしいものです。
Dmitry Grigoryev

5

それ以上の損傷を保護すること、または不整合な状態になることは意図的です。


1

ここで他のすべての優れた回答とは別に、いくつかのケースも追加したいと思います。

独自の例外を作成する場合は、メッセージを追加できます

あなた自身を投げるなら、あなたNullPointerExceptionはメッセージを加えることができます(あなたは間違いなくそうするべきです!)

デフォルトのメッセージは、たとえばnullfrom new NullPointerException()とそれを使用するすべてのメソッドですObjects.requireNonNull。そのnullを出力すると、空の文字列に変換されることさえあります...

少し短くて情報がない...

スタックトレースは多くの情報を提供しますが、ユーザーが何がnullであるかを知るには、コードを掘り起こして正確な行を確認する必要があります。

ここで、NPEがラップされ、ネット経由で送信されたと想定します。たとえば、おそらく異なる部門間または組織間で、Webサービスエラーのメッセージとして送信されます。最悪のシナリオ、誰が何をnull表すのか理解できないかもしれません...

連鎖メソッド呼び出しはあなたが推測し続けるでしょう

例外は、例外が発生した行についてのみ通知します。次の行について考えてみます。

repository.getService(someObject.someMethod());

あなたは、NPEを取得し、それは次のいずれか、この行を指している場合repositorysomeObjectヌルでしたか?

代わりに、これらの変数を取得したときにそれらをチェックすると、少なくともそれらが処理されている唯一の変数である行をポイントします。そして、前述のように、エラーメッセージに変数の名前または類似の名前が含まれている場合はさらに良いでしょう。

大量の入力を処理する際のエラーは、識別情報を提供するはずです

プログラムが数千行の入力ファイルを処理していて、突然NullPointerExceptionが発生したとします。場所を見て、一部の入力が正しくないことに気付きました...どの入力ですか?そのファイルのどの行を修正する必要があるかを理解するには、行番号、おそらく列または行テキスト全体についての詳細情報が必要になります。

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