【初心者】JavaのNullPointerExceptionの犯人探し

# はじめに
Javaの開発者なら誰でも見覚えのあるNullPointerException。初心者がNullPointerExceptionに遭遇すると、見当違いな部分を直してしまったり、デバッグに時間をかけてしまうことがあります。また、ある程度Javaに慣れた方でも、なんとなくでNullPointerExceptionに対応している方もいるのではないでしょうか。

この記事では、NullPointerExceptionが発生した際に、どのオブジェクトがnullでNullPointerExceptionが起きているのか、スタックトレースとソースコードのみで特定する方法をまとめました。

# 問題

さて、まずは問題です。下記にNullPointerExceptionが発生したソースとスタックトレースを記載しました。Java11を使って確認しています。

```java
public class NPESample {

    public static void main(String[] args) {

        new SampleObj().getHoge().doMoge(getFuga());//NullPointerException

    }
/*以下略*/
```

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

何がnullでNullPointerExceptionが起きたかわかりますか？（複数回答可）
1. new SampleObj()の結果
2. getHoge()の戻り値
3. doMoge()の戻り値
4. getFuga()の戻り値


# 正解 

正解は2のみです。
これに自信をもって回答できなかった方が、この記事の対象読者となります。


# 基本パターン：ピリオドの左がnull

まず、NullPointerExceptionの定義についてですが、JavaDocには下記のようにあります。

>オブジェクトが必要な場合に、アプリケーションがnullを使おうとするとスローされます。たとえば、次のような場合があります。
>nullオブジェクトのインスタンス・メソッドの呼出し。
>nullオブジェクトのフィールドに対するアクセスまたは変更。
>nullの長さを配列であるかのように取得。
>nullのスロットを配列であるかのようにアクセスまたは修正。
>nullをThrowable値であるかのようにスロー。
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/NullPointerException.html

このうち最頻なのが、`nullオブジェクトのインスタンス・メソッドの呼出し`だと思います。
これはソースコード上では`nullのオブジェクト.メソッド`の形になります。
簡単に言うと、NullPointerExceptionでは、 **ピリオドの左側がnull** だということです。
これを覚えておけばNullPointerExceptionの９割は犯人を推察することができます。

`new SampleObj().getHoge().doMoge(getFuga());`
**ピリオドの左側がnull** の法則に照らし合わせると、回答は`1. new SampleObj()の結果`
`2. getHoge()の戻り値`に絞り込めます。

では、`1. new SampleObj()の結果`はなぜ違うのか。
Javaのnewは生成したオブジェクトを返すか、例外をスローするかのどちらかしかなく、nullを返してくることはありません。そのため、2が回答となります。

なお、`nullオブジェクトのフィールドに対するアクセスまたは変更。`は`nullのオブジェクト.フィールド`ですし、`nullの長さを配列であるかのように取得`は`nullのオブジェクト.length`ですので、これらも**ピリオドの左側がnull**に当てはまります。

# スタックトレースの最上段に注目
`4. getFuga()の戻り値`を選択してしまった方もいるかと思うのですが、
getFuga()の戻り値がnullだったとしても、getFugaにはピリオドはついていないためすぐにNullPointerExceptionは起きません。
起きるとしたら、`doMoge(getFuga())`のdoMogeの中で引数に対して何かをしている場合ですが、その場合は、スタックトレースは下記のようになります。
```
Exception in thread "main" java.lang.NullPointerException
	at SampleObj$Hoge.doMoge(SampleObj.java:10)
	at NPESample.main(NPESample.java:5)
```
最上段が
`at NPESample.main(NPESample.java:5)`ではなく、`	at SampleObj$Hoge.doMoge(SampleObj.java:10)`になっており、doMogeの中でNullPointerExceptionが起きたことになります。
今回の問題は、`at NPESample.main(NPESample.java:5)`でしたので、４は違うことがわかります。

このように、NullPointerExceptionの調査では、 **スタックトレースの最上段の行** を見るのが重要です。最上段の行をソースから探し、 **ピリオドの左側がnull** になる可能性がある場所を探せば、なにがnullなのかがわかります。

# その他のパターン

ここからは **ピリオドの左側がnull** 以外の代表的なNullPointerException発生パターンを記載します。

## 角括弧の左がnull

`nullのスロットを配列であるかのようにアクセスまたは修正。`に該当すると思われますが、

```
a[0]
```
のような箇所でaがnullだとNullPointerExceptionが発生します

## 拡張for文

```
for(Sample sample : sampleList) {
```
のような拡張for文でsamleListがnullだとNullPointerExceptionが発生します。
内部でsamleList.iterator()を呼んでいるからと理解すれば納得できますね。

## primitiveへのキャスト

```
Integer integer = null;
int i = (int)integer;
```
このようにラッパー型のnullをprimitive型にキャストする場合もnullが起きます。
オートボクシングの場合も同様です。


# まとめ

NullPointerExceptionに出会ったら、 **スタックトレースの最上段** のソースを見て **ピリオドの左側がnull** を真っ先に疑うようにしましょう。
これを知っていれば、デバッグのできない環境でも、スタックトレースとソースコードだけで、NullPointerExceptionの犯人につかづくことができます。

# おまけ

Java14からは、NullPointerExceptionのメッセージが親切になり、何がnullかを懇切丁寧に教えてくれるようです。

https://qiita.com/nowokay/items/ec85d97a7cecaaac8123#358-helpful-nullpointerexceptions