オートボクシングで起きる NullPointerException
Integerはnullを持てますが、演算時にintへ開封されます。Map.getの戻り値の受け方を扱います。
エラーメッセージの読み方
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 Integer n = null; ^ 4 System.out.println(n + 1); 5 } 6 }
Integerはnullを持てますが、演算時にintへ自動開封されます。nullの開封はNullPointerExceptionです。
直し方: Integer を Integer にします。
パターン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("missing"); ^ 8 System.out.println(v); 9 } 10 }
getはキーがなければnullを返します。intで受けると開封が走って落ちるため、Integerで受けるか既定値を使います。
直し方: int を Integer にします。
パターン3
1 import java.util.List; 2 3 public class Main { 4 public static void main(String[] args) { 5 List<Integer> list = List.of(10, 20, 30); 6 list.remove(Integer.valueOf(1)); ^ 7 System.out.println(list); 8 } 9 }
Listのremoveはintとオブジェクトで意味が変わる数少ない例です。意図をキャストで明示します。
直し方: Integer.valueOf(1) を Integer.valueOf(1) にします。
パターン4
1 public class Main { 2 public static void main(String[] args) { 3 Integer a = null; 4 boolean condition = false; 5 int result = condition ? 1 : a ; ^ 6 System.out.println(result); 7 } 8 }
三項演算子の結果をintで受けると、選ばれた側のIntegerがnullなら自動開封の瞬間に例外になります。
直し方: a を 0 にします。
パターン5
1 public class Main { 2 public static void main(String[] args) { 3 Integer[] nums = new Integer[3] ; ^ 4 int sum = 0; 5 for (Integer n : nums) sum += n; 6 System.out.println(sum); 7 } 8 }
Integer配列の要素の既定値はnullです(intのように0にはなりません)。合計する前に必ず値を入れます。
直し方: new Integer[3] を new Integer[]{1, 2, 3} にします。
よくある誤解
NullPointerExceptionなのにnullを書いた記憶がない場合、自動開封を疑ってください。ラッパー型がintに変換される瞬間に起きます。
実務での勘所
オートボクシングによるNullPointerExceptionは特殊な例外ではなく、コンパイラがInteger.intValue()という通常のメソッド呼び出しを自動的に挿入しているだけです。int x = map.get("missing");のようなコードは、実際にはint x = map.get("missing").intValue();とほぼ同じ形にコンパイルされます。JDK 14以降の詳細なエラーメッセージでは「Cannot invoke "java.lang.Integer.intValue()" because ... is null」のように、自分では書いていないintValue()の呼び出しがそのまま表示されるため、これを知っていれば見覚えのないメソッド名への戸惑いがなくなります。