Code Fix

中級

NullPointerException の原因と直し方

nullに対する操作で発生します。発生箇所の特定方法と、そもそもnullを持ち回さない設計を扱います。

エラーメッセージの読み方

Exception in thread "main" java.lang.NullPointerException:
main
スレッド名 — mainなら起動直後の処理で発生しています
java.lang
パッケージ — java.lang なら標準の基本例外です
NullPointerException
例外クラス — 何が起きたか。ここを検索するのが最短です

このエラーが出る典型パターン

パターン1

 1  public class Main {
 2      public static void main(String[] args) {
 3          String s = null;
                         ^
 4          System.out.println(s.length());
 5      }
 6  }
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is nullexited with code 1

nullは実体がない状態です。空文字""は長さ0のStringという実体なので、メソッドを呼べます。

直し方: null を "" にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  import java.util.HashMap;
 2  import java.util.Map;
 3  
 4  public class Main {
 5      public static void main(String[] args) {
 6          Map<String, Integer> m = new HashMap<>();
 7          int v = m.get("x")            ;
                                ^
 8          System.out.println(v);
 9      }
10  }
Exception in thread "main" java.lang.NullPointerExceptionexited with code 1

存在しないキーのgetはnullを返します。intへの代入時に自動開封が起きてNullPointerExceptionになります。

直し方: get("x") を getOrDefault("x", 0) にします。

この問題を解いてみる →

パターン3

 1  public class Main {
 2      static String find(int n) {
 3          if (n > 0) return "found";
 4          return null;
                     ^
 5      }
 6      public static void main(String[] args) {
 7          System.out.println(find(-1).length());
 8      }
 9  }
Exception in thread "main" java.lang.NullPointerExceptionexited with code 1

nullを返すメソッドは呼び出し側にnullチェックを強制します。空の値を返すほうが安全です。

直し方: null を "" にします。

この問題を解いてみる →

パターン4

 1  public class Main {
 2      static class Address { String city = "Tokyo"; }
 3      static class Person { Address address                ; }
                                                     ^
 4      public static void main(String[] args) {
 5          Person p = new Person();
 6          System.out.println(p.address.city);
 7      }
 8  }
Exception in thread "main" java.lang.NullPointerException: Cannot read field "city" because "<local1>.address" is nullexited with code 1

参照型フィールドの既定値はnullです。使う前に必ずnewで実体を作ります。

直し方: (空) を = new Address() にします。

この問題を解いてみる →

パターン5

 1  public class Main {
 2      public static void main(String[] args) {
 3          Boolean flag = null   ;
                              ^
 4          if (flag) System.out.println("yes");
 5      }
 6  }
Exception in thread "main" java.lang.NullPointerExceptionexited with code 1

if文の条件はbooleanへ自動開封されます。Booleanがnullだと開封できず例外になります。

直し方: null を false にします。

この問題を解いてみる →

よくある誤解

nullを代入した行では落ちません。使った行で落ちます。スタックトレースの先頭が示すのは「使った場所」です。

実務での勘所

実務では単一の原因より、複数の経路が重なって起きることが多いです。Map.get()は該当キーが無いとき例外ではなくnullを返すため、存在チェックを忘れると次の行で落ちます。a.getB().getC()のようなメソッドチェーンは、途中の呼び出しがnullを返した瞬間にどこで落ちたか分かりにくくなります。コンストラクタの中で他のインスタンスメソッドを呼ぶと、そのメソッドが参照するフィールドがまだ初期化されていないことがあります。JDK 14でHelpful NullPointerExceptions(JEP 358)が導入され、JDK 15からは既定で有効になりました。「どの変数がnullだったか」がエラー文にそのまま書かれるようになったのはこのためで、このサイトのエラー文もこの形式です。JDK 14より前、またはJDK 14でオプションを明示的に有効化していない環境では、原因の説明がなく「Exception in thread "main" java.lang.NullPointerException」の次にスタックトレースが1行出るだけです。この短い形式しか出ない場合は、スタックトレースの行番号から自力で原因の行を特定してください。

演習をはじめる

関連するエラー

他の言語ではどうなるか

広告
広告スロット(未設定)