NullPointerExceptionとは何ですか、どうすれば修正できますか?
ヌルポインタ例外(java.lang.NullPointerException
)とは何ですか?また、その原因は何ですか?
例外によってプログラムが途中で終了するのを防ぐために、原因を特定するためにどのような方法/ツールを使用できますか?
回答
参照変数(つまりオブジェクト)を宣言すると、実際にはオブジェクトへのポインターが作成されます。プリミティブ型の変数を宣言する次のコードについて考えてみますint
。
int x;
x = 10;
この例では、変数x
はでありint
、Javaがそれを初期化0
します。10
2行目にの値を割り当てると、の値がで10
参照されるメモリ位置に書き込まれx
ます。
しかし、参照型を宣言しようとすると、別のことが起こります。次のコードを取ります。
Integer num;
num = new Integer(10);
最初の行は、という名前の変数を宣言していますがnum
、実際にはまだプリミティブ値が含まれていません。代わりに、ポインタが含まれています(型がInteger
参照型であるため)。何を指すかをまだ言っていないので、Javaはそれをnull
に設定します。これは「私は何も指していません」という意味です。
2行目では、new
キーワードを使用してタイプのオブジェクトをインスタンス化(または作成)Integer
し、ポインター変数num
をそのInteger
オブジェクトに割り当てています。
NullPointerException
あなたは、変数を宣言しますが、オブジェクトを作成し、(と呼ばれる変数の内容を使用しようとする前に、それを変数に割り当てていない場合に発生するデリファレンスを)。つまり、実際には存在しないものを指しているのです。
逆参照は通常、を使用.
してメソッドまたはフィールドにアクセスするとき、またはを使用[
して配列にインデックスを付けるときに発生します。
num
オブジェクトを作成する前に逆参照しようとすると、が得られますNullPointerException
。最も些細なケースでは、コンパイラーが問題をキャッチして「num may not have been initialized
、」と通知しますが、オブジェクトを直接作成しないコードを作成する場合もあります。
たとえば、次のような方法があります。
public void doSomething(SomeObject obj) {
//do something to obj, assumes obj is not null
obj.myMethod();
}
この場合、オブジェクトを作成するのではなくobj
、doSomething()
メソッドが呼び出される前に作成されたと想定します。次のようなメソッドを呼び出すことができることに注意してください。
doSomething(null);
その場合、obj
はnull
、であり、ステートメントobj.myMethod()
はNullPointerException
。をスローします。
上記のメソッドのように、渡されたオブジェクトに対してメソッドが何かを行うことを意図している場合、それはNullPointerException
プログラマーエラーであり、プログラマーはデバッグ目的でその情報を必要とするため、をスローするのが適切です。
NullPointerException
メソッドのロジックの結果としてスローされることに加えて、メソッドの引数のnull
値を確認し、メソッドの先頭近くに次のようなものを追加することでNPEを明示的にスローすることもできます。
//Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
エラーメッセージで、どのオブジェクトを使用できないかを明確に示すと役立つことに注意してくださいnull
。このような検証を行うことの利点は、1)独自のより明確なエラーメッセージを返すことができ、2)obj
再割り当てされない限り、nullではなく、安全に参照解除できることがわかっている残りのメソッドについてです。
あるいは、メソッドの目的が渡されたオブジェクトを操作することだけではない場合があるため、nullパラメーターが受け入れられる場合があります。この場合、nullパラメータをチェックして、動作を変える必要があります。また、ドキュメントでこれを説明する必要があります。たとえば、次のdoSomething()
ように書くことができます。
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj == null) {
//do something
} else {
//do something else
}
}
例外によってプログラムが途中で終了するのを防ぐために、原因を特定するためにどのような方法/ツールを使用できますか?
検索バグのあるソナーはNPEを検出できます。 ソナーはJVMによって引き起こされたnullポインタ例外を動的にキャッチできますか
現在、Java 14には、NullPointerExceptionの根本原因を示す新しい言語機能が追加されています。この言語機能は、2006年からSAP商用JVMの一部となっています。以下は、この驚くべき言語機能を理解するために2分間読んだものです。
https://jfeatures.com/blog/NullPointerException
Java 14では、以下はNullPointerException例外メッセージのサンプルです。
スレッド「main」内java.lang.NullPointerException:「list」がnullであるため、「java.util.List.size()」を呼び出すことができません
NullPointerException
sは、オブジェクトを参照しているかのように、メモリ内の場所がない(null)参照を使用しようとしたときに発生する例外です。null参照でメソッドを呼び出すか、null参照のフィールドにアクセスしようとすると、がトリガーされますNullPointerException
。これらは最も一般的ですが、他の方法はNullPointerException
javadocページにリストされています。
おそらく、私が思いついた最も簡単なサンプルコードは次のようにNullPointerException
なります。
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
最初の行の内側にはmain
、私は明示的に設定していますObject
参照obj
に等しいですnull
。これは、参照があるが、オブジェクトを指していないことを意味します。その後、メソッドを呼び出して、オブジェクトを指しているかのように参照を処理しようとします。これNullPointerException
は、参照が指している場所で実行するコードがないために発生します。
(これは技術的なことですが、言及する必要があると思います:nullを指す参照は、無効なメモリ位置を指すCポインタと同じではありません。nullポインタは文字通りどこも指していません。これは微妙に異なります。たまたま無効な場所を指しています。)
NullPointerExceptionとは何ですか?
開始するのに適した場所はJavaDocsです。彼らはこれをカバーしています:
オブジェクトが必要な場合に、アプリケーションがnullを使用しようとしたときにスローされます。これらには以下が含まれます:
- nullオブジェクトのインスタンスメソッドを呼び出す。
- nullオブジェクトのフィールドへのアクセスまたは変更。
- nullの長さを配列であるかのように取ります。
- nullのスロットにアクセスするか、配列であるかのように変更します。
- Throwable値であるかのようにnullをスローします。
アプリケーションは、このクラスのインスタンスをスローして、nullオブジェクトの他の不正な使用を示す必要があります。
JLSに従って、を使用してnull参照を使用しようとするとsynchronized
、この例外もスローされる場合もあります。
SynchronizedStatement: synchronized ( Expression ) Block
- それ以外の場合、式の値がnullの場合、a
NullPointerException
がスローされます。
どうすれば修正できますか?
だからあなたは持っていNullPointerException
ます。どのように修正しますか?NullPointerException
:をスローする簡単な例を見てみましょう。
public class Printer {
private String name;
public void setName(String name) {
this.name = name;
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
null値を特定する
最初のステップは、例外の原因となっている値を正確に特定することです。このために、いくつかのデバッグを行う必要があります。スタックトレースの読み方を学ぶことが重要です。これにより、例外がスローされた場所が表示されます。
Exception in thread "main" java.lang.NullPointerException
at Printer.printString(Printer.java:13)
at Printer.print(Printer.java:9)
at Printer.main(Printer.java:19)
ここでは、例外が13行目(printString
メソッド内)でスローされていることがわかります。行を見て、ロギングステートメントを追加するかデバッガーを使用して、どの値がnullであるかを確認します。それs
がnullであることがわかり、そのlength
メソッドを呼び出すと例外がスローされます。s.length()
がメソッドから削除されると、プログラムが例外のスローを停止することがわかります。
これらの値がどこから来ているかをトレースします
次に、この値がどこから来ているかを確認します。メソッドの呼び出し元に従うことにより、我々はその参照s
で渡されたprintString(name)
にprint()
する方法、およびthis.name
nullです。
これらの値を設定する場所をトレースします
どこにthis.name
設定されていますか?ではsetName(String)
方法。さらにデバッグを行うと、このメソッドがまったく呼び出されていないことがわかります。メソッドが呼び出された場合は、これらのメソッドが呼び出される順序を確認してください。setメソッドはprintメソッドの後に呼び出されません。
これは私たちに解決策を与えるのに十分です:を呼び出すprinter.setName()
前にに呼び出しを追加しますprinter.print()
。
その他の修正
変数はデフォルト値を持つことができます(そしてsetName
それがnullに設定されるのを防ぐことができます):
private String name = "";
print
またはprintString
メソッドのいずれかがnullをチェックできます。次に例を示します。
printString((name == null) ? "" : name);
または、name
常にnull以外の値を持つようにクラスを設計することもできます。
public class Printer {
private final String name;
public Printer(String name) {
this.name = Objects.requireNonNull(name);
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer("123");
printer.print();
}
}
参照:
私はまだ問題を見つけることができません
問題をデバッグしようとしても解決策がない場合は、質問を投稿してさらにヘルプを求めることができますが、これまでに試したことを必ず含めてください。少なくとも、質問にスタックトレースを含め、コードで重要な行番号をマークします。また、最初にコードを単純化してみてください(SSCCEを参照)。
質問:NullPointerException
(NPE)の原因は何ですか?
あなたが知っておくべきこととして、Javaタイプが分かれているプリミティブ型(boolean
、int
など)と参照型。Javaの参照型を使用するnull
と、「オブジェクトなし」というJavaの言い方である特別な値を使用できます。
NullPointerException
プログラムnull
が実際の参照であるかのようにを使用しようとすると、実行時にAがスローされます。たとえば、これを書く場合:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
「HERE」というラベルの付いたステートメントはlength()
、null
参照に対してメソッドを実行しようとしますNullPointerException
。これにより、がスローされます。
null
結果がNullPointerException
。になる値を使用する方法はたくさんあります。実際には、あなたがいることを唯一のものができて行うnull
NPEを発生させずには、以下のとおりです。
- それを参照変数に割り当てるか、参照変数から読み取ります。
- それを配列要素に割り当てるか、配列要素から読み取ります(配列参照自体がnullでない場合)。
- パラメータとして渡すか、結果として返すか、または
==
または!=
演算子、またはを使用してテストしinstanceof
ます。
質問:NPEスタックトレースを読み取るにはどうすればよいですか?
上記のプログラムをコンパイルして実行するとします。
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
最初の観察:コンパイルは成功します!プログラムの問題はコンパイルエラーではありません。それは、実行時エラーが発生しました。(一部のIDEは、プログラムが常に例外をスローすることを警告する場合があります...しかし、標準javac
コンパイラはスローしません。)
2番目の観察:プログラムを実行すると、2行の「gobbledy-gook」が出力されます。違う!!それはゴツゴツしたグックではありません。これはスタックトレースです...そして、時間をかけて注意深く読むと、コードのエラーを追跡するのに役立つ重要な情報を提供します。
それで、それが何を言っているか見てみましょう:
Exception in thread "main" java.lang.NullPointerException
スタックトレースの最初の行は、いくつかのことを示しています。
- 例外がスローされたJavaスレッドの名前が表示されます。1つのスレッド(このような)を持つ単純なプログラムの場合、それは「メイン」になります。次へ移りましょう ...
- スローされた例外のフルネームが表示されます。すなわち
java.lang.NullPointerException
。 - 例外に関連するエラーメッセージがある場合、それは例外名の後に出力されます。
NullPointerException
エラーメッセージが表示されることはめったにないため、この点では異常です。
2行目は、NPEの診断で最も重要な行です。
at Test.main(Test.java:4)
これは私たちに多くのことを教えてくれます:
- 「atTest.main」は、私たちがクラスの
main
メソッドに入っていたことを示していTest
ます。 - 「Test.java:4」はクラスのソースファイル名を示し、これが発生したステートメントがファイルの4行目にあることを示しています。
上記のファイルの行数を数えると、4行目が「ここ」のコメントでラベル付けした行です。
より複雑な例では、NPEスタックトレースに多数の行があることに注意してください。ただし、2行目(最初の「at」行)は、NPEがスローされた場所を示していることを確認できます1。
つまり、スタックトレースは、プログラムのどのステートメントがNPEをスローしたかを明確に示します。
1-完全に真実ではありません。ネストされた例外と呼ばれるものがあります...
質問:コード内のNPE例外の原因を追跡するにはどうすればよいですか?
これは難しい部分です。簡単な答えは、スタックトレース、ソースコード、および関連するAPIドキュメントによって提供される証拠に論理的推論を適用することです。
最初に簡単な例(上記)で説明しましょう。まず、スタックトレースがNPEが発生した場所を示している行を確認します。
int length = foo.length(); // HERE
どのようにそれはNPEを投げることができますか?
実際、唯一の方法foo
がありますnull
。値がの場合にのみ発生する可能性があります。次に、length()
メソッドを実行してみnull
ます... BANG!
しかし(あなたが言うのを聞きます)NPEがlength()
メソッド呼び出しの中に投げられたらどうなるでしょうか?
そうだとすれば、スタックトレースは異なって見えるでしょう。最初の「at」行は、java.lang.String
クラスのある行で例外がスローされたTest.java
ことを示し、の4行目は2番目の「at」行になります。
それで、それはnull
どこから来たのですか?この場合、それは明らかであり、それを修正するために何をする必要があるかは明らかです。(null以外の値をに割り当てますfoo
。)
では、もう少しトリッキーな例を試してみましょう。これには、論理的な推論が必要になります。
public class Test {
private static String[] foo = new String[2];
private static int test(String[] bar, int pos) {
return bar[pos].length();
}
public static void main(String[] args) {
int length = test(foo, 1);
}
}
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.test(Test.java:6)
at Test.main(Test.java:10)
$
これで、2つの「at」行ができました。最初のものはこの行のためのものです:
return args[pos].length();
2番目はこの行用です:
int length = test(foo, 1);
最初の行を見ると、どのようにしてNPEをスローできますか?2つの方法があります:
- 値が場合
bar
でnull
、その後bar[pos]
NPEをスローします。 - の値
bar[pos]
がnull
それを呼び出しlength()
ている場合、NPEがスローされます。
次に、これらのシナリオのどれが実際に何が起こっているのかを説明しているのかを理解する必要があります。最初のものを探索することから始めます:
どこbar
から来たの?これはtest
メソッド呼び出しのパラメーターであり、呼び出された方法を見るtest
と、foo
静的変数からのものであることがわかります。さらに、foo
null以外の値に初期化したことがはっきりとわかります。この説明を暫定的に却下するには、これで十分です。(理論的には、他の何かが...に変わる 可能性foo
がありnull
ますが、それはここでは発生していません。)
では、2番目のシナリオはどうですか?まあ、それはであることがわかります、それpos
は1
それがでfoo[1]
なければならないことを意味しますnull
。これは可能ですか?
確かにそうです!そしてそれが問題です。このように初期化すると:
private static String[] foo = new String[2];
に初期化されるString[]
2つの要素でnull
を割り当てます。その後、foo
...の内容は変更していませんので、foo[1]
引き続きnull
。
であるオブジェクトにアクセスしようとしているようなものですnull
。以下の例を検討してください。
TypeA objA;
この時点で、このオブジェクトを宣言したばかりですが、初期化またはインスタンス化されていません。そして、その中のプロパティやメソッドにアクセスしようとすると、それはNullPointerException
意味のあるものをスロー します。
以下の例も参照してください。
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
オブジェクトが必要な場合にアプリケーションがnullを使用しようとすると、nullポインタ例外がスローされます。これらには以下が含まれます:
null
オブジェクトのインスタンスメソッドを呼び出す。null
オブジェクトのフィールドへのアクセスまたは変更。- の長さを
null
配列のように取ります。 null
配列であるかのようにのスロットにアクセスまたは変更します。- 投げ
null
それはThrowableの値であるかのように。
アプリケーションは、このクラスのインスタンスをスローして、null
オブジェクトの他の不正な使用を示す必要があります。
参照:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
null
ポインタは、1ポイントはどこにあるという。ポインタを逆参照するp
ときp
は、「「p」に格納されている場所のデータを教えてください。がnull
ポインタの場合、に格納されている場所p
はnowhere
です。「どこにもない場所のデータを教えてください」と言います。明らかに、これを行うことはできないので、をスローしnull pointer exception
ます。
一般に、何かが正しく初期化されていないことが原因です。
それがどのように発生し、どのように修正するかを説明するために、すでに多くの説明がありますが、を回避するためのベストプラクティスにも従う必要がありますNullPointerException
。
参照: ベストプラクティスの優れたリスト
非常に重要なことですが、final
修飾子をうまく利用します。
Javaで適用可能な場合は常に「final」修飾子を使用する
概要:
final
修飾子を使用して、適切な初期化を強制します。- 該当する場合は空のコレクションを返すなど、メソッドでnullを返さないようにします。
- 注釈
@NotNull
を使用して@Nullable
- 速く失敗し、アサートを使用して、nullであってはならないときにアプリケーション全体にnullオブジェクトが伝播するのを防ぎます。
- 最初に既知のオブジェクトとequalsを使用します。
if("knownObject".equals(unknownObject)
- を優先
valueOf()
しtoString()
ます。 - nullセーフ
StringUtils
メソッドを使用しますStringUtils.isEmpty(null)
。 - メソッドの戻り値としてJava8オプションを使用します。オプションクラスは、null参照の代わりにオプション値を表すためのソリューションを提供します。
Javaでは、すべて(プリミティブ型を除く)がクラスの形式になっています。
オブジェクトを使用する場合は、次の2つのフェーズがあります。
- 宣言する
- 初期化
例:
- 宣言:
Object object;
- 初期化:
object = new Object();
アレイの概念についても同じです。
- 宣言:
Item item[] = new Item[5];
- 初期化:
item[0] = new Item();
初期化セクションを指定していない場合は、NullPointerException
発生します。
ヌルポインタ例外は、オブジェクトを初期化せずに使用していることを示します。
たとえば、以下はコードで使用する学生クラスです。
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
以下のコードは、nullポインタ例外を示します。
public class School {
Student student;
public School() {
try {
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
を使用しstudent
ているが、以下に示す正しいコードのように初期化するのを忘れたためです。
public class School {
Student student;
public School() {
try {
student = new Student();
student.setId(12);
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
でJavaのあなたが宣言するすべての変数は、実際のオブジェクト(またはプリミティブ)といないオブジェクト自体への「参照」です。
1つのオブジェクトメソッドを実行しようとすると、参照は生きているオブジェクトにそのメソッドを実行するように要求します。ただし、参照がNULL(nothing、zero、void、nada)を参照している場合、メソッドが実行される方法はありません。次に、ランタイムはNullPointerExceptionをスローしてこれを通知します。
あなたの参照はnullを「指す」、つまり「Null-> Pointer」です。
オブジェクトはVMメモリスペースに存在し、オブジェクトにアクセスする唯一の方法はthis
参照を使用することです。この例を見てください:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
そして、あなたのコードの別の場所で:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
これは知っておくべき重要なことです。オブジェクトへの参照がなくなると(上記の例ではreference
、otherReference
両方がnullを指している場合)、オブジェクトは「到達不能」になります。それを操作する方法がないため、このオブジェクトはガベージコレクションの準備ができており、ある時点で、VMはこのオブジェクトによって使用されているメモリを解放し、別のメモリを割り当てます。
NullPointerException
オブジェクト配列を宣言し、すぐにその中の要素を逆参照しようとすると、別のaが発生します。
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
この特定のNPEは、比較順序を逆にすると回避できます。つまり、.equals
保証されたnull以外のオブジェクトで使用します。
配列内のすべての要素は、共通の初期値に初期化されます。どのタイプのオブジェクト配列でも、すべての要素がnull
。であることを意味します。
要素にアクセスまたは逆参照する前に、配列内の要素を初期化する必要があります。
String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}