そもそも null とは何なのか

Javaでは、 変数に値が入っているとは限りません。

オブジェクト型の変数には、 「どのオブジェクトも参照していない」 状態があります。

String name = null;

この状態では、 name は文字列オブジェクトを 参照していません。

nullは「空文字」でも「0」でもありません。 「参照するオブジェクトが存在しない」 という状態です。

なぜ NullPointerException が起きるのか

問題になるのは、 nullの状態の変数に対して メソッドを呼び出した場合です。

String name = null;

System.out.println(
    name.length()
);

name は どのStringオブジェクトも 参照していません。

その状態で name.length() を実行しようとするため、 Javaは処理対象となるオブジェクトを 見つけられません。

Exception in thread "main"
java.lang.NullPointerException

nullをイメージすると分かりやすい

例えば、

String name = null;

という状態を、 「住所だけあって家が存在しない」 と考えてみます。

name null
name.length() 実行できない

「nameという変数は存在する」 けれど、
「nameが参照しているオブジェクトは存在しない」
という状態です。

業務システムではどこで起きるのか

実際の業務システムでは、 nullを直接書いているケースよりも、 「処理の途中でnullになっていた」 というケースの方が多くあります。

User user = findUser(id);

String name = user.getName();

一見すると問題なさそうですが、 findUser(id) が ユーザーを見つけられなかった場合、 user がnullになる可能性があります。

User user = findUser(id);

if (user != null) {

    String name = user.getName();

}

このように、 オブジェクトが存在することを確認してから メソッドを呼び出します。

POINT

NullPointerExceptionの原因を探すときは、 エラーになった行だけを見るのではなく、 「その変数にどこから値が入ってきたのか」 を追うことが重要です。

エラーが出たら何を見るか

NullPointerExceptionが発生した場合、 まず見るのはスタックトレースです。

java.lang.NullPointerException
    at UserService.getUserName(UserService.java:25)
    at Main.main(Main.java:10)

ここで重要なのは、

UserService.java の25行目

という情報です。

ただし、 25行目だけを修正すれば終わりとは限りません。

例えば、

User user = userRepository.findById(id);

return user.getName();

というコードなら、 問題は getName() ではなく、 その前の findById(id) がnullを返していることかもしれません。

「nullチェック」だけでは終わらない

初心者の頃は、 NullPointerExceptionを見ると とりあえずnullチェックを追加したくなります。

if (user != null) {

    // 処理

}

もちろん必要な場合もあります。

ただし、 本当に重要なのは、

「なぜuserがnullになる可能性があるのか?」

を考えることです。

DBにデータがないのか。 APIのレスポンスに含まれていないのか。 条件分岐を通っていないのか。 初期化されていないのか。

原因によって、 本来取るべき対応は変わります。

エラーが起きた場所と、
原因は同じとは限らない。

NullPointerExceptionで 「この行が悪い」と思って修正したら、 別の場所で同じエラーが出る。

こういうことは、 業務システムでは珍しくありません。

重要なのは、 nullになった瞬間を探すこと。
そのためには、 メソッドの呼び出し元、 DBの検索結果、 APIレスポンスなど、 データの流れを追っていく必要があります。

NullPointerExceptionを理解する

NullPointerExceptionは、 単純に「nullだから起きるエラー」 と覚えるだけでは少しもったいありません。

大切なのは、

変数 null
処理 オブジェクトを要求

という状態になっていないかを見ることです。

そして実際の開発では、 「どこでnullになったのか」を追うことが エラー解決の第一歩になります。