読者です 読者をやめる 読者になる 読者になる

気まま研究所ブログ

思ったことをてきとーに書きます。

NullPointerExceptionとその対応

お久しぶりです。
しばらく忙しくて全くブログやら個人のプロジェクトに全く力を注げませんでした。

さて、今回はJavaぬるぽについてお話ししていきます。
つい最近知り合いにぬるぽについて軽く話す機会があったのですが、イマイチ伝わってなかったようなので記事更新がてらこちらに長々と書いていきます。
ごく当たり前の内容ばかり書くので知ってる方にはかなり退屈な内容になると思います。

ぬるぽって何だ

Javaをやっていると、「ぬるぽぬるぽ」という言葉をよく耳にします。ごく当たり前のように発していますが、ぬるぽとは一体何なのでしょうか。

ぬるぽとは、NullPointerExceptionの略称で、ヌルポインタを参照しようとした時に発生する例外のことです。
ちょっと意味不明ですね、下の方で詳しく触れていきます。

ヌルポインタとは

上でも出てきましたが、ヌルポインタとは何でしょうか。

ヌルポインタとは、どのオブジェクトも指していないポインタのことです。
どういうこっちゃ。

Javaでは基本的にクラスはnewでインスタンスを作りますね。
この時、代入される変数には生成されたインスタンスのポインタが割り当てられ、どこにそのインスタンスがあるかが明確になっています。
対して、ヌルポインタはどのオブジェクトも指さない、つまりどこにも存在しないポインタです。
C言語にもポインタがありますが、そのNULLと同じものと考えて問題ないです。

今回のキーワードはヌルポインタです。

具体的にぬるぽについて見てみる

具体的にヌルポインタとNullPointerExceptionについて見ていきましょう。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = new Test();
        t.i = 0;

        int i = t.i.intValue();
        System.out.println(i);
    }
}
0

結果はおそらく0と出力されたはずです。
順番に見ていきましょう。

Test t = new Test();

ここでTestクラス型の変数tにTestクラスのインスタンスのポインタが割り当てられます。

t.i = 0;

ここでは変数tのポインタにあるTestクラスのインスタンスのInteger iに0が代入されます。

System.out.println(t.i.intValue());

そして、最後にt.iの値を参照して表示させます。

図示するとこんな感じです。 f:id:AonaSuzutsuki:20160731201900p:plain f:id:AonaSuzutsuki:20160731201903p:plain


以上を踏まえた上で、ここからぬるぽ発生させていきます。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = null;
        t.i = 0;

        int i = t.i.intValue();
        System.out.println(i);
    }
}
Exception in thread "main" java.lang.NullPointerException
    at NullPo.main(NullPo.java:7)

出ました、やつが出ました。
おそらくt.i = 0;の行で発生したと思います。

順番に見ていきましょう。
まず、Test t = nullにて変数tにヌルポインタを代入しています。
その次に、ヌルポインタのiに0を代入しようとしています。
ここが問題なんです。

f:id:AonaSuzutsuki:20160731201904p:plain

t.iはtのポインタを参照し、iに値を代入する、という処理を行います。
今回、tはnullですので、どこの場所も指していません。
存在しないインスタンスのiを変更しようとしてるのですから、正しく動作するはずがありませんね。


もう一つ例をあげてみましょう。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = new Test();
        t.i = null;

        int i = t.i.intValue();
        System.out.println(i);
    }
}
Exception in thread "main" java.lang.NullPointerException
    at NullPo.main(NullPo.java:9)

次はt.i.intValue()で発生したと思います。
原理は上と同じですが、今回はTestクラスのインスタンスは作成されていて、その中の値がnullのパターンです。

f:id:AonaSuzutsuki:20160731201906p:plain

変数tには実在するインスタンスのアドレスが格納されているのでt.iへの代入は正しく動作します。
しかしながら、t.iへは今回nullを代入しているので、t.iの値・メソッドすべてに関して参照しようとするとぬるぽが発生します。

結局ぬるぽって何なのか

ぬるぽNullPointerExceptionのことで、ヌルポインタを参照した時に発生します。
ヌルポインタはどこも指さない特殊なポインタですからヌルポインタを参照するということは「どこにも存在しないものを参照しようとしている」ということです。
どこにも存在しないポインタを参照しようとしているのですから、当然処理は続けられません。
現実世界でもないものを取ったりすることは不可能ですよね、コンピュータもまた同じです。
NullPointerExceptionは指定された場所に何もないよ!見つからないよ!というメッセージなのです。

ぬるぽの対応と予防

ぬるぽについては以上の通りです。ここからが肝心、どのように予防・対応すれば良いのでしょうか。

予防に関しては単純にnullであるかを確認するだけです。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = new Test();
        t.i = 0;

        if (t != null) {
            System.out.println(t.i);
        }
    }
}

if構文にてtがnull出ない時、参照処理をさせるようにすると、ぬるぽの発生を未然に防ぐことができます。
これをnullチェックと俗に呼んでいます。


nullチェック目的で用いるのは推奨できませんが、try catch構文を使うのもまた一つの手です。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = null;

        try {
            System.out.println(t.i);
        }
        catch (NullPointerException nullEx) {  }
    }
}

こうすることでもぬるぽは「表に」出なくなります。
表にを強調したのには理由があって、実際には例外が発生しているのですが、それをユーザーの目にはエラーメッセージとして見えないようにしています。
今回は処理がないので全く見えませんが。

try catchは元来例外が出てしまった時に特定の処理をさせたい時に用います。
例えば、「エラー出たよー、もう一度確認してトライしてね」みたいな独自のエラーメッセージを出したり、例外が出たことをログに書き込んだりします。
本来、例外は結構重い処理なようで、極力発生させないようにすることが望ましいです。
ですので、今回は例外の予測ができてますので使うべきではありません。
予測できる場合はnullチェックを使いましょう。
参考程度に載せておきます。


さて、次にぬるぽが出てしまった時の対応を考えます。
開発中に出るはずのないぬるぽが出ることはよくある話です。
しかしながら、複雑化したソースですと、なかなか原因がわからないこともしばしば。
特に、初めての場合だとどこを見れば良いのかわからなくなると思います。
以下では私が行っているぬるぽ潰しの方法を載せます。

class Test {
    Integer i;
}
public class NullPo {
    public static void main(String[] args) {
        Test t = Init();
        t.i = 0;
        showTestValue(t);
    }

    public static Test Init() {
        return null;
    }
    public static void showTestValue(Test t) {
        System.out.println(t.i.intValue());
    }
}

上のコードを例にします。
上のコードは実行するとt.i = 0でNullPointerExceptionが発生します。
この時、どこがnullになっているでしょうか。

tですね。
tがnullということは、tに値が代入されるところが問題となり、Initメソッドが原因とわかります。

このように、ぬるぽはどこがnullとなっているかを把握することが重要となります。
それがわかれば、問題の候補はかなり絞られるはずです。
あとはその中のどこでnullが入ったかをじっくり確認しながら原因を探り当てます。

以上が私が行っているぬるぽ潰しです。
まとめると、まずは「何がnullとなっているのか」を見つけます。
次に、そのnullの変数に代入されるメソッドなどをチェックします。
この時、ところどころプレークポイントを置きながらするとわかりやすいです。
あとはこれを繰り返して原因の場所を突き止めます。

最後に

以上がぬるぽの簡単な説明と、その対応でした。
今回はJavaに絞った話をしましたが、C#やその他の言語でも言える話だったりします。
C#ではNullReferenceExceptionがJavaにおけるぬるぽにあたり、同様の方法で回避・対処ができます。
身内向けの記事になりましたが、参考になればと思います。