SJ blog
beginner
S

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

nullとは何か — NullPointerExceptionが起きる根本理由と向き合い方

nullとは何か、なぜ存在するのか、なぜNullPointerExceptionが起きるのかを参照の仕組みから解説。Optionalの存在理由と、nullと安全に付き合う方法を初心者向けに解説。

一言結論

nullとは「参照先が存在しない」状態。参照型変数はメモリアドレスを持つが、nullはアドレスがない状態であり、そこにメソッドを呼ぶとNPEになる。「値がない」の表現としてOptionalが設計された。

nullを発明した人の後悔

nullを最初に発明したのはトニー・ホア(Tony Hoare)という計算機科学者だ。1965年のこと。

彼は後にこう語っている:

「私の10億ドルの失敗(My Billion-Dollar Mistake)」

null参照を発明したことで、以来何十年もの間、数え切れないほどのバグ、脆弱性、システム障害が引き起こされてきた、と。

なぜそこまで言われるのか。nullの仕組みを理解しよう。

nullとは「参照先がない」状態だ

前の記事(==とequals()の違い)で説明したように、参照型変数は**メモリアドレス(オブジェクトがある場所)**を格納している:

String s = "hello";

変数s: [ 0x100 ]  ← 「0x100番地を見ろ」

        [ "hello" ]  ← 実体はここ

nullは「アドレスが存在しない」状態だ:

String s = null;

変数s: [ null ]  ← 「どこも指していない」

「どこも指していない」変数に対してメソッドを呼び出そうとすると:

String s = null;
int len = s.length();  // ← 「s が指す場所にある length() を呼ぶ」 → 指す場所がない!

JVMは「どこを探せばいいのか」わからない。これが NullPointerException(NPE) だ。例外名の通り「nullのポインタ(参照)に対してアクセスしようとした」エラーだ。

なぜnullが必要なのか

ではnullをなくせばいいか?なくせない理由がある。

「値がない」という状態をどう表現するかという問題だ。

例えばデータベースで「名前が未登録のユーザー」を表したいとき:

// nullなしではどう表現する?
String name = "";        // 空文字?でも "" と「未登録」は別物だ
String name = "UNKNOWN"; // マジックワード?判定が面倒で間違いやすい
String name = null;      // ← これが「値がない」の自然な表現

参照型はオブジェクトを指すか指さないかのどちらかしかない。「指さない」を表現する手段がnullだ。プリミティブ型(intなど)にnullがないのは、値を直接格納するので「指す」「指さない」という概念がそもそもないからだ。

NPEが起きる主なパターン

パターン1:初期化忘れ

String name;  // 宣言だけして初期化していない(フィールドはnullになる)

int len = name.length();  // NPE

フィールドは宣言時にデフォルト値が入る(参照型はnull、intは0)。ローカル変数は初期化前に使おうとするとコンパイルエラー。

パターン2:メソッドがnullを返す

String result = findUser("未登録のID");  // ユーザーが見つからない → null を返す
System.out.println(result.toUpperCase());  // NPE

「見つからなかったらnullを返す」というパターンは非常に多い。呼び出し側がnullチェックを忘れると落ちる。

パターン3:配列の要素

String[] names = new String[3];  // 要素はすべてnull
System.out.println(names[0].length());  // NPE

配列の要素は参照型なら全部nullで初期化される。

防衛的プログラミング

古典的な対処法はnullチェックだ:

String result = findUser(id);
if (result != null) {
    System.out.println(result.toUpperCase());
} else {
    System.out.println("ユーザーが見つかりません");
}

でも「nullを返す可能性があるメソッド」が増えると、至る所にnullチェックが散らばる。コードが汚くなる。しかもチェックを忘れたときにコンパイラは何も言わない。

Java 8のOptional:「nullかもしれない」を型で表現する

Java 8で導入されたOptional<T>は「値がある場合とない場合の両方を型で表現する」コンテナだ:

// nullを返す代わりにOptionalを返す
Optional<String> findUser(String id) {
    String user = db.find(id);
    return Optional.ofNullable(user);  // nullをOptional.empty()に変換
}

// 呼び出し側
Optional<String> result = findUser(id);

// 値があるか確認してから使う
result.ifPresent(user -> System.out.println(user.toUpperCase()));

// または orElse でデフォルト値
String user = result.orElse("ゲスト");

// または例外を投げる
String user = result.orElseThrow(() -> new UserNotFoundException(id));

Optionalを使うと「このメソッドはnullを返すかもしれない」が型で明示されるStringなら「ある」、Optional<String>なら「あるかもしれない」と型を見るだけでわかる。

String         → 必ずある(nullが来たらバグ)
Optional<String> → ない場合もある(必ずないケースを考慮せよ)

ただしOptionalは万能ではない。フィールドやメソッド引数で使うのは設計的に避けるのが一般的で、「メソッドの戻り値」として使うのが主な用途だ。

Java 14以降のNPEメッセージ改善

Java 14以降、NPEのメッセージが大幅に改善された:

String name = user.getAddress().getCity().toUpperCase();
// Java 13以前
NullPointerException

// Java 14以降
NullPointerException: Cannot invoke "String.toUpperCase()" because
the return value of "Address.getCity()" is null

どのメソッドがnullを返したかが一目瞭然になった。デバッグが格段に楽になっている。

NPEに出会ったときの読み方

Exception in thread "main" java.lang.NullPointerException
    at Main.processOrder(Main.java:42)
    at Main.main(Main.java:10)

スタックトレースを下から読む:

  1. main()processOrder()を呼んだ(Main.java:10)
  2. processOrder()の42行目でNPEが発生した

42行目を見て、どの変数がnullの可能性があるかを探す。複数の参照をつなげているなら一つずつ分解して確認する。

// 複合式でNPEが出た場合
String city = order.getCustomer().getAddress().getCity();  // どこがnull?

// 分解して確認
Customer customer = order.getCustomer();
System.out.println("customer: " + customer);  // nullか?
Address address = customer.getAddress();
System.out.println("address: " + address);    // nullか?
String city = address.getCity();

まとめ

nullとは         = 参照先が存在しない状態(アドレスがない)
NPEの原因        = null参照に対してメソッドを呼ぶ(どこも指していない場所へのアクセス)
なぜnullがあるか  = 「値がない」を参照型で表現する手段がほかにないから
プリミティブにnullなし = 値を直接格納するので「指す」「指さない」の概念がない
Optional        = 「nullかもしれない」を型で表現する(Java 8以降)
対処法           = nullチェック・Optional・nullが返らない設計(null不変クラス)

nullは「存在しない」を表す唯一の手段として不可欠だが、「存在しない可能性を考慮し忘れる」という人間の認知の穴を突く。Optionalやnullオブジェクトパターンなど、nullを「型で見える化」するアプローチが現代では好まれる。それでもnullは消えないので、参照型には「nullが来うる」という前提を持つ習慣が大切だ。