「null安全」とは――Javaプログラマーが血と汗と涙を流さなくてすむ理由Android Studioで始めるKotlin入門(3)(1/2 ページ)

Android Studio 3.0を使い、最近話題のプログラミング言語「Kotlin」の特徴を解説する連載。今回は、特にJavaプログラマーに注目していただきたい「null安全」について解説します。

» 2018年03月15日 05時00分 公開

 Android Studioを使い、Kotlin言語の特徴を解説する本連載「Android Studioで始めるKotlin入門」。連載第1回では、Kotlinの大まかな構文を説明しました。前回からは、Kotlin言語の個々の機能について解説しています。

 Kotlin入門の3回目となる今回は、特にJavaプログラマーに注目していただきたい「null安全」について解説します。多くのプログラマーを苦しめ続けてきたnullの扱いですが、Kotlinを使うとスマートにコードを書くことができるようになるでしょう。

 まずはnull安全が必要な背景から考えていきます。

nullあるある

 突然ですが質問です。「産まれてこの方、自分の書いたコードでNullPointerExceptionが起きるのを見たことがない」というJavaプログラマーはどのぐらいいるのでしょうか。書籍などのサンプルコードを入力しながらJavaを勉強し、いよいよ実際に動作するアプリケーションを自分で書いていくプロセスの中で、まずほとんどのプログラマーがこの例外に幾度となく悩まされたことでしょう。Javaではnull値の参照型変数(プリミティブ型ではない変数)を参照すると、このNullPointerExceptionが発生します。

 初学者にとって悩ましいのは、参照型ではないプリミティブ型の変数においては、この例外は一切発生せず、参照型(オブジェクト)を扱うようになって初めて、nullの概念と併せてNullPointerExceptionを回避する手段を覚えなければならないことです。基本的な対処法は「ちゃんと初期化する」「参照型変数を使うときはnullチェックを欠かさない」などです。多くのJavaプログラマーが血と汗と涙とともに、Null参照を回避する方法を身体で学習することになります。

 とはいえ、NullPointerExceptionが悩ましいのは初学者だけではありません。例えば自分が作ったクラスを、チームのメンバーに使ってもらうようなケースはよくあるでしょう。リスト1のように簡単にメソッドを書いて、自分でテストします。

void doSomething(ParamObj obj){
    string name = obj.name;
    System.out.println("お名前は: " + name + "ですね");
}
リスト1 対象を引数で受け取るメソッド。nullが渡されたら???

 あるとき同僚に「君の書いたクラスでNullPointerException出るんだけど!」と文句を言われるのです。コードを調べたところ、呼び出し元が引数objにnullを渡してきているのが原因でした。ちょっとイラッとしながらリスト2のようにコメントを書きます。IDEで読めるように、ちょっと親切にJavaDocにしてみました。

/**
 * doSomethingメソッド
 * 名前を出力します
 * @param  obj パラメーターオブジェクト。注意! nullを渡さないこと!!!
 */
void doSomething(ParamObj obj){……}
リスト2 JavaDocコメントで注意を促す

 でも、数日後また同僚に「NullPointerException出るんだけど! コメント? そんなの知らないよ」と言われます。「いやいや呼び出し元で気を付けてよ、というかJavaDocコメントぐらい読んでよ」と思いつつ、リスト3のように修正するわけです。

void doSomething(ParamObj obj){
    if(obj == null){
        throw new IllegalArgumentException("obj引数にはnullは指定できません");
    }
……
}
リスト3 nullを渡されたら例外を返す強硬策に出てみる

 このdoSomethingメソッドは引数にnullが渡されるケースは想定しなくていい(と設計者である自分は考えた)ので、想定外のケースには「引数が正しくないよ」という意味でIllegalArgumentException例外を投げることにしました。すると、また怒られます。

 「スゴく大事な製品コードでいきなりIllegalArgumentExceptionが出てアプリケーション落ちちゃったよ!」

 悪いのは呼び出し側なのに、と思いつつ、仕方なくリスト4のようなコードに変更します(*1)。

*1)nullが渡されるべきではない場合に、nullチェック後の処理としてどうすべきかはさまざまな意見があるようです。前述のように例外を投げる実装も選択肢の1つです。

void doSomething(ParamObj obj){
    if(obj == null){
        return; //何もせず終了
    }
……
}
リスト4 nullを渡されたら黙って何もしないことに

 これで呼び出し元から怒られることがなくなって一件落着……と言えるほどすっきりはしませんね。実装者として何とも釈然としない思いが残ります。本来「nullが渡されるケースは設計上意味を持たない」にもかかわらず、万が一のNullPointerExceptionを避けるためだけに、nullが渡された場合の処理を何かしら書かなければならないのです。

 ここに示したのはあくまで氷山の一角ですが、Javaプログラマーは参照型変数を使うたびに「もしかしたらこれはnullかもしれない」と疑心暗鬼になりながら、nullチェックコードを山のように積んでいかなければなりません。

 もちろん、nullが常に悪者というわけではありません。例えばリスト5のようなコードでは、nullチェックが重要な役割を果たします。

public class Singleton{
    Singleton instance;
    public Singleton getInstance(){ //唯一のインスタンスを取得するメソッド
        if(instance == null){ //nullチェックして
            instance = new Singleton(); //nullなら新しいインスタンスを作る
        }
        return instance; //インスタンスを返す
    }
    private Singleton(){} //外部からインスタンスを作れないようにする
}
リスト5 Singletonコード例

 これはSingletonパターンと呼ばれる「あるクラスのインスタンスが常に1つであることを保証する」デザインパターンの“伝統的”な実装例です(*2)。ここでは「instanceフィールドがnullである場合はインスタンスを生成して返し、そうではない場合はそのままinstanceフィールドを返す」実装になっています。

*2)この書き方はマルチスレッド動作の際に誤動作する可能性があるため現在では推奨されません。あくまでもnullチェックが明確に意味を持つ例としてご覧ください。

 先ほどのコードとの違いは何でしょうか? いろいろな説明の仕方がありますが、例えば前者は「そもそも変数がnullであってはならない(=nullであるケースに意味がない)のに、言語レベルでnullを排除できないため、やむなく自前でnullチェックを行っている」のに対し、後者は「ある変数がnullであることに意味があるので、nullチェックで分岐して処理している」ケースと言い換えられるかもしれません。

 このNull参照は多くの問題をもたらしました。Null参照の発明者とされるアントニー・ホーア氏自身が2009年に「それ(Null参照)は10億ドルにも相当する私の誤りだ」と述べたほどです。

 このNull参照につきまとう問題を解決するため、さまざまな努力が払われてきました。Kotlinを含め、Swift、TypeScript 2といったモダンな言語では、言語レベルでnull安全を保証しています。Java自身もJava 8で「Optional」というクラスを追加し、プログラムの堅牢性を高め、煩雑な記述を緩和する方向へと動いていますし、C#は今後のバージョンで、言語レベルでのnull安全を保証することを検討しているようです。

Kotlinのnull安全の仕組み〜「非Null型」と「Null許容型」の下克上!?〜

 やや枕が長くなってしまいましたが、いよいよKotlinのnull安全の仕組みに入っていきましょう。Kotlinでは言語自体にnull安全の仕組みが用意されており、「ある変数がnullではないことを言語レベルで保証」できます。そのアイデアは実にシンプルなもので、「nullを許容する参照型とnullを許容しない参照型を明確に分ける」というものです。

 リスト6を見てみましょう。

var a: String = "はろー" //String型の変数
a = null //コンパイルエラー!?
リスト6 String型にnullを代入……できない

 Javaプログラマーであれば、このコードがコンパイルできないのは不可解に思えるでしょう。しかし、Kotlinにおいては2行目のnull代入でコンパイルエラーが発生し、このコードはコンパイルできません。nullを代入したいなら、リスト7のように書く必要があります。今度は、コンパイルエラーは発生しません。

var b: String? = "はろー"
b = null //String?型ならばnullが代入可能
リスト7 String?型にはnullを代入できる

 Kotlinにおいては「型名」だけで宣言した変数はnullを許容しない「非Null型」(Non-Null Type)として扱われ、「型名?」のように型名の後に「?」を付けることで「Null許容型」(Nullable Type。Javaの参照型はこちらに該当)として扱われます。つまり「新たに非Null型という特別な型を作る」ことをせず、「非Null型が特殊なのではなく、むしろNull許容型の方が特殊なのだ」という逆転の発想で「基本は非Null型を使って、nullが必要な場合にだけNull許容型を使ってください」ということにしたのです。

 この発想の転換は見事で、「nullを前提にした世界」を「明示しない限りnullがない世界」に作り替えてしまったといっていいでしょう。

 このため、nullを扱う必要がないなら、非Null型だけを使ってプログラムを書くようにしましょう。そうすればNull参照に苦しめられることはありません。例えば、前述のJavaで取り上げたケースでは、引数のobjを非Null型にするだけで問題が解決します。

       1|2 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。