NullPointerExceptionを理解する方法

この簡単な記事は、Java開発の初心者向けの可能性が高いですが、NullPointerException(略してNPE)を報告するスタックトレースを無力に見て、デバッガーなしでは結論を出せない経験豊富な同僚をよく見かけます。 もちろん、アプリケーションをNPEに持ち込まない方がよいでしょう。ヌルアノテーション、入力パラメーターの検証、その他のメソッドが役立ちます。 しかし、患者がすでに病気になっているとき、彼を治療する必要があり、彼が冬に帽子なしで歩いた脳に滴り落ちないようにする必要があります。



そのため、アプリケーションがNPEでクラッシュし、スタックトレースしかないことがわかりました。 おそらく、あなたは彼のクライアントから送られたか、あなた自身がログで彼を見ました。 それからどのような結論が導き出せるのか見てみましょう。



NPEは、次の3つの場合に発生する可能性があります。

  1. 彼は投げを使って投げられました
  2. 誰かが投げてヌルを投げた
  3. 誰かがヌルリンクにアクセスしようとしています


2番目と3番目のケースでは、例外オブジェクトのメッセージは常にnullです。最初のケースでは、メッセージは任意です。 たとえば、nullをキーとして渡した場合、java.lang.System.setPropertyはメッセージ「key can not null」を含むNPEをスローします。 メソッドの各入力パラメーターを同じ方法でチェックし、明確なメッセージで例外をスローする場合、この記事の残りは必要ありません。



ヌル参照は、次の場合に発生する可能性があります。

  1. 非静的クラスメソッドの呼び出し
  2. 非静的フィールドへのアクセス(読み取りまたは書き込み)
  3. 配列要素へのアクセス(読み取りまたは書き込み)
  4. 配列の読み取り長
  5. ボックス化解除中のvalueOfメソッドの暗黙的な呼び出し


これらのケースは、スタックトレースが終了する行で発生し、他の場所では発生しないことを理解することが重要です。



次のコードを検討してください。

1: class Data { 2: private String val; 3: public Data(String val) {this.val = val;} 4: public String getValue() {return val;} 5: } 6: 7: class Formatter { 8: public static String format(String value) { 9: return value.trim(); 10: } 11: } 12: 13: public class TestNPE { 14: public static String handle(Formatter f, Data d) { 15: return f.format(d.getValue()); 16: } 17: }
      
      





いくつかのパラメーターを持つhandleメソッドがどこかから呼び出され、次のようになりました:

 Exception in thread "main" java.lang.NullPointerException at TestNPE.handle(TestNPE.java:15)
      
      





例外の理由は何ですか-f、d、またはd.valですか? formatメソッドは静的であるため、この行のfがまったく読み取られないことは簡単にわかります。 もちろん、クラスのインスタンスを介して静的メソッドにアクセスするのは悪いことですが、そのようなコードが発生します(たとえば、リファクタリング後に表示される可能性があります)。 何らかの方法で、fの値が例外の原因になることはありません。 dがnullでなく、d.valがnullの場合、formatメソッド内(9行目)で既に例外が発生しています。 同様に、問題がより複雑であっても、getValueメソッドの内部に問題を置くことはできません。 例外が15行目にあると、考えられる理由が1つあります。dパラメータにnullがあります。



別の例を次に示します。

  1: class Formatter { 2: public String format(String value) { 3: return "["+value+"]"; 4: } 5: } 6: 7: public class TestNPE { 8: public static String handle(Formatter f, String s) { 9: if(s.isEmpty()) { 10: return "(none)"; 11: } 12: return f.format(s.trim()); 13: } 14: }
      
      





もう一度handleメソッドを呼び出して取得します

 Exception in thread "main" java.lang.NullPointerException at TestNPE.handle(TestNPE.java:12)
      
      





現在、formatメソッドは非静的であり、fがエラーの原因になります。 ただし、sはソースの下にはありません。9行目には、sへのアピールがすでにありました。 sがヌルの場合、9行目で例外が発生していました。 例外の前にコードのロジックを表示すると、多くの場合、一部のオプションを破棄するのに役立ちます。



もちろん、ロジックでは、注意する必要があります。 9行目の条件が次のように記述されているとします。

 if("".equals(s))
      
      





現在、行自体はフィールドとメソッドsにアクセスできず、equalsメソッドは正しくnullを処理してfalseを返すため、この場合、12行目のエラーはfとsの両方によって引き起こされる可能性があります。 親コードを分析するときは、ドキュメントまたはソースで、使用されるメソッドと構成がnullにどのように応答するかを指定します。 たとえば、文字列連結演算子+は、NPEを呼び出しません。



コードは次のとおりです(Javaバージョンがここで役割を果たす可能性があります。OracleJDK 1.7.0.45を使用します)。

  1: import java.io.PrintWriter; 2: 3: public class TestNPE { 4: public static void dump(PrintWriter pw, MyObject obj) { 5: pw.print(obj); 6: } 7: }
      
      





dumpメソッドを呼び出すと、次の例外が発生します。

 Exception in thread "main" java.lang.NullPointerException at java.io.PrintWriter.write(PrintWriter.java:473) at java.io.PrintWriter.print(PrintWriter.java:617) at TestNPE.dump(TestNPE.java:5)
      
      





pwパラメータをnullにすることはできません。そうしないと、printメソッドを入力できません。 objでnullですか? pw.print(null)が文字列「null」を例外なく印刷することを確認するのは簡単です。 最後から行きましょう。 ここで例外が発生しました:

 472: public void write(String s) { 473: write(s, 0, s.length()); 474: }
      
      





行473には、NPEの考えられる1つの理由のみがあります。行sのlengthメソッドの呼び出しです。 したがって、sにはnullが含まれます。 これはどうして起こるのでしょうか? 上記のスタックを上に移動します。

 616: public void print(Object obj) { 617: write(String.valueOf(obj)); 618: }
      
      





String.valueOfメソッドを呼び出した結果は、writeメソッドに渡されます。 どの場合にnullを返すことができますか?

 public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
      
      





唯一の可能なオプションは、objがnullではないが、obj.toString()がnullを返したことです。 したがって、MyObjectオブジェクトのオーバーライドされたtoString()メソッドでエラーを探す必要があります。 MyObjectはスタックトレースにまったく表示されませんでしたが、問題はあります。 このような単純な分析により、デバッガーで状況を再現しようとする時間を大幅に節約できます。



陰湿なオートボクシングを忘れないでください。 次のコードがあります。

  1: public class TestNPE { 2: public static int getCount(MyContainer obj) { 3: return obj.getCount(); 4: } 5: }
      
      





そしてそのような例外:

 Exception in thread "main" java.lang.NullPointerException at TestNPE.getCount(TestNPE.java:3)
      
      





一見、objパラメーターの唯一のオプションはnullです。 ただし、MyContainerクラスを確認する必要があります。

 import java.util.List; public class MyContainer { List<String> elements; public MyContainer(List<String> elements) { this.elements = elements; } public Integer getCount() { return elements == null ? null : elements.size(); } }
      
      





getCount()がIntegerを返すことがわかります。これはTestNPE.javaの3行目で自動的にintになります。つまり、getCount()がnullを返す場合、これはまさに例外です。 MyContainerクラスに類似したクラスを見つけた場合は、バージョン管理システムの作成者が誰であるかを調べ、パンくずをふりかけます。



メソッドがintパラメータを受け入れ、Integer nullを渡すと、メソッドが呼び出される前にボックス化が行われるため、NPEは呼び出しのある行を指すことに注意してください。



結論として、私はデバッガーをあまり頻繁に実行したくないと思います:いくつかのトレーニングの後、頭の中のコードの分析はしばしばとらえどころのない状況の再現よりも高速です。



All Articles