JavaのNullPointerExceptionの検出、修正、およびベストプラクティス

java.lang.NullPointerExceptionは、Javaプログラミングにおいて非常に人気のある例外の一つです。Javaで作業している人なら、JavaのスタンドアロンプログラムやJavaのウェブアプリケーションでいきなり現れることを目にしたことがあるでしょう。

java.lang.NullPointerExceptionを日本語で言い換えると、次の通りです:ヌルポインタ例外

java lang nullpointerexception hierarchy

NullPointerExceptionはランタイム例外なので、プログラム内でキャッチする必要はありません。NullPointerExceptionは、nullが必要な場所でnullに対して操作を行おうとしたときに、アプリケーションで発生します。JavaプログラムにおけるNullPointerExceptionの一般的な原因のいくつかは次の通りです。

    1. オブジェクトインスタンス上のメソッドを呼び出すが、実行時にオブジェクトが null です。

 

    1. 実行時に null となっているオブジェクトインスタンスの変数にアクセスしています。

 

    1. プログラム内で null を投げます。

 

    1. null となっている配列のインデックスにアクセスまたは値を修正しています。

 

    実行時に null となっている配列の長さをチェックしています。

Java の NullPointerException の例

– Java の NullPointerException の例
– Java の NullPointerException のサンプル
– Java の NullPointerException の具体例

Javaプログラムの中で、NullPointerExceptionのいくつかの例を見てみましょう。

1. インスタンスメソッドを呼び出す際のNullPointerException

public class Temp {

	public static void main(String[] args) {

		Temp t = initT();
		
		t.foo("Hi");
		
	}

	private static Temp initT() {
		return null;
	}

	public void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

上記のプログラムを実行すると、NullPointerExceptionのエラーメッセージが発生します。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

「t.foo(“Hi”);」という文でNullPointerExceptionが発生していますが、ここでは「t」がnullです。

2. ヌルオブジェクトのフィールドにアクセス/変更する際のJavaのNullPointerException

public class Temp {

	public int x = 10;
	
	public static void main(String[] args) {

		Temp t = initT();
		
		int i = t.x;
		
	}

	private static Temp initT() {
		return null;
	}

}

上記のプログラムは、次のNullPointerExceptionのスタックトレースを投げます。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:9)

「NullPointerException」は、「t」がここでnullであるため、文「int i = t.x;」でスローされます。

3. メソッドの引数にnullが渡された場合のJavaのNullPointerException。

public class Temp {

	public static void main(String[] args) {

		foo(null);

	}

	public static void foo(String s) {
		System.out.println(s.toLowerCase());
	}
}

これは、java.lang.NullPointerExceptionの中でも最も一般的な発生方法の一つです。なぜなら、nullの引数を渡しているのは呼び出し元の方だからです。

上記プログラムがEclipse IDEで実行された際、以下の画像はnullポインターエラーを示しています。

Exception in thread main java.lang.NullPointerException

4. null が投げられた場合の java.lang.NullPointerException

public class Temp {

	public static void main(String[] args) {

		throw null;
	}

}

上記のプログラムの例外スタックトレースが以下に示されています。throw null; 文のため、NullPointerExceptionが発生しました。

Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:5)

5. nullの配列の長さを取得する際のNullPointerException

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data.length;
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

6. nullの配列のインデックス値にアクセスするとNullPointerExceptionが発生します。

public class Temp {

	public static void main(String[] args) {

		int[] data = null;
		
		int len = data[2];
	}

}
Exception in thread "main" java.lang.NullPointerException
	at Temp.main(Temp.java:7)

7. nullオブジェクト上で同期化した際のNullPointerException

public class Temp {

	public static String mutex = null;
	
	public static void main(String[] args) {

		synchronized(mutex) {
			System.out.println("synchronized block");
		}		
	}
}

「synchronized(mutex)」は、「mutex」というオブジェクトがnullであるため、NullPointerExceptionが発生します。

8. HTTP ステータス500 java.lang.NullPointerException

時々、私たちはJavaウェブアプリケーションからエラーページの応答を受け取ります。エラーメッセージは「HTTPステータス500 – サーバーエラー」となり、原因はjava.lang.NullPointerExceptionです。

これについて、
Spring MVCの例のプロジェクトを編集し、HomeControllerメソッドを以下のように変更しました。

@RequestMapping(value = "/user", method = RequestMethod.POST)
	public String user(@Validated User user, Model model) {
		System.out.println("User Page Requested");
		System.out.println("User Name: "+user.getUserName().toLowerCase());
		System.out.println("User ID: "+user.getUserId().toLowerCase());
		model.addAttribute("userName", user.getUserName());
		return "user";
	}

以下の画像は、Webアプリケーションのレスポンスが返された際に表示されるエラーメッセージを示しています。

http error 500 java lang nullpointerexception

以下は例外のスタックトレースです。 (Kore wa reigai no sutakku torēsu desu.)

HTTP Status 500Internal Server Error

Type Exception Report

Message Request processing failed; nested exception is java.lang.NullPointerException

Description The server encountered an unexpected condition that prevented it from fulfilling the request.

Exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
	org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
	org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Root Cause

java.lang.NullPointerException
	com.scdev.spring.controller.HomeController.user(HomeController.java:38)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	java.lang.reflect.Method.invoke(Method.java:498)

ルートの原因は、user.getUserId().toLowerCase()の文で発生するNullPointerExceptionです。なぜなら、user.getUserId()がnullを返しているからです。

Java.lang.NullPointerExceptionを検出する方法

NullPointerExceptionを検出するのは非常に簡単です。例外のトレースを見れば、例外のクラス名と行番号が表示されます。そして、コードを見て、NullPointerExceptionの原因となる可能性があるnullを見つけます。上記の例をすべて見れば、スタックトレースから何がnullポインターエクセプションの原因かが非常に明確にわかります。

NullPointerException を修正する方法は何ですか。

java.lang.NullPointerExceptionは、チェックしなくても良い例外ですので、私たちはキャッチしなくても結構です。nullポインターの例外は、nullのチェックと予防的なコーディング技術を使って防ぐことができます。以下のコード例を見て、java.lang.NullPointerExceptionを回避する方法をご覧ください。

if(mutex ==null) mutex =""; //preventive coding
		
synchronized(mutex) {
	System.out.println("synchronized block");
}
//using null checks
if(user!=null && user.getUserName() !=null) {
System.out.println("User Name: "+user.getUserName().toLowerCase());
}
if(user!=null && user.getUserName() !=null) {
	System.out.println("User ID: "+user.getUserId().toLowerCase());
}

ヌルポインターエクセプションを回避するためのコーディングのベストプラクティス

以下の関数を考え、nullポインター例外を引き起こす可能性のあるシナリオに注意しましょう。

public void foo(String s) {
    if(s.equals("Test")) {
	System.out.println("test");
    }
}

引数が null として渡される場合、NullPointerException が発生する可能性があります。同じメソッドは、以下のように書くことでNullPointerExceptionを避けることができます。

public void foo(String s) {
	if ("Test".equals(s)) {
		System.out.println("test");
	}
}

2. 必要に応じて、引数のヌルチェックも追加し、IllegalArgumentException をスローすることもできます。

public int getArrayLength(Object[] array) {
	
	if(array == null) throw new IllegalArgumentException("array is null");
	
	return array.length;
}

3. 下記の例コードに示されているように、三項演算子を使用することができます。

String msg = (str == null) ? "" : str.substring(0, str.length()-1);

4. toString()メソッドの代わりにString.valueOf()メソッドを使用してください。たとえば、PrintStreamのprintln()メソッドのコードを以下のように定義しましょう。

public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
        }
    }

以下のコードスニペットは、valueOf()メソッドをtoString()の代わりに使用する例を示しています。

Object mutex = null;

//prints null
System.out.println(String.valueOf(mutex));

//will throw java.lang.NullPointerException
System.out.println(mutex.toString());

できる限り、nullの代わりに空のオブジェクトを返すメソッドを書いてください。例えば、空のリストや空の文字列などです。

6. NullPointerExceptionを防ぐために、コレクションクラスにはいくつかのメソッドが定義されていますので、それらを使用するべきです。例えばcontains()、containsKey()、containsValue()などです。

参照:APIドキュメント

コメントを残す 0

Your email address will not be published. Required fields are marked *