プログラマに優しい現実指向JVM言語 Kotlin入門

第5回 null安全

この記事を読むのに必要な時間:およそ 5 分

前回はKotlinにおけるクラスとその周辺の機能,文法を紹介しました。今回はKotlinのユニークな機能であるnull安全について解説します。

背景

java.lang.NullPointerExceptionは,Javaプログラマがよく出会う例外でお馴染みです。オブジェクトが必要な場面でnullを使用してしまうことでスローされる例外です。具体的にはString型の変数にnullを代入しておき,その変数に対してlengthメソッドを呼び出した場合にNullPointerException⁠ぬるぽ」の愛称で親しまれる例外(以下,NPEと表記)が投げられます。

nullは,値が存在しないときに使用されます。たとえば,指定したIDを持ったユーザが存在しないときにfindUserByIdのようなメソッドがUserクラスのインスタンスを返す代わりにnullを返すと言った具合です。

このような観点で,nullは便利に働きます。しかしこのnullのおかげで筆者たちは見たくもない例外,NPEと遭遇するはめになるのです。適切にnullチェック,つまりif文などでnullでないことを確認すれば回避できるのですが,なぜそれができないのでしょうか。

nullを返さないことがわかっているメソッドの戻り値に対してnullチェックはしないのが普通だと思います。ここが重要なのですが,仕様的にnullを返し得ないメソッドがあっても,Javaのコードでそのことを保証することはできません。Javaにおいて,変数やメソッドの戻り値はいつでもどこでもnullになり得ます。つまりnullチェックをすべきものと,nullチェックが不要なものがごちゃまぜになっているので,うっかりNPEを招いてしまうのです。

nullと上手に付き合う方法

nullかもしれないものと,nullではないものを区別するための方法が世の中にはいくつかあります。

メソッドシグネチャの工夫

原始的な方法です。nullを返す可能性があるメソッドのシグネチャを工夫して,プログラマに注意喚起します。たとえばgetNameOrNullのような名前のメソッドです。名前を見ればnullが返されるかもしれないことに気づくわけです。

静的解析ツール

メソッドにアノテーションを付けて,静的解析ツールに指摘してもらう方法です。getNameメソッドがnullを返すかもしれない場合には@Nullable String getName() {...}と記述し,nullを返し得ない場合には@Nonnull StringgetName() {...}と記述します。

型で表現

存在しない可能性のある値を表現するためにnullの代わりに新しく定義した型を使う方法です。具体的にはJava SE 8で導入されたjava.util.Optionalクラスです。値が存在しないときにはOptional#emptyで返されるオブジェクトを使用し,値が存在するときはその値をOptional#ofの引数に渡してラップします。Optional型とそれ以外の型で,存在しないかもしれない値と絶対に存在する値の区別が容易になるだけでなく,Optionalにはさまざまな便利なメソッドが提供されています。

Kotlinのnull安全

静的解析ツールやOptionalを使うことはとても良いことです。しかし繰り返しになりますが,Javaにおいて変数やメソッドの戻り値はいつでもどこでもnullになり得ます。すべてを台無しにするコードをご覧くださいリスト1)⁠

リスト1 誰にもnullは止められない!

// Javaコードです
@Nonnull
Optional<String> getName() {
  return null;
}

そこでKotlinのnull安全機構の登場です。Kotlinではnullの可能性のある値(以下Nullable)nullではない値(以下NotNull)の区別を言語組込みの機能としてサポートします。

Optionalを使う方法とは異なり,新しいインスタンスの生成(とGC)が不要なのでその分のオーバーヘッドがなく,Androidなどのリソースが限られた環境で有利のようです。

基本的な使い方

前回,前々回と使用してきたごく普通の変数初期化と再代入をリスト2に示します。変数aString型です。ここでは型を明示していますが省略しても問題ありません。varキーワードにより変更可能な変数として宣言しているので"Goodbye"を代入できます。しかしその次の行のnullを代入する部分でコンパイルエラーが起こります。変数aはNotNullとして宣言されているのでnullの代入をコンパイラが許しません! 逆を言えば,変数aは常にnullではないと安心して使用できます。

リスト2 NotNullの変数

var a: String = "Hello"
a = "Goodbye"
a = null // ここでコンパイルエラー

では,nullを代入できるNullableな変数はどのように宣言すれば良いのでしょうか。簡単です。通常の型アノテーションのあとに?を置くだけです。リスト3を見てください。変数bの型アノテーションがString?になっています。これはnullが代入可能なString型」と読めます。2行目でnullを代入していますが,コンパイルに成功します。今回の場合,変数bの型アノテーションを省略できないことに注意してください。なぜなら,"Hello"String?ではなくStringとして推論されるからです。

リスト3 Nullableの変数

var b: String? = "Hello"
b = null

KotlinではNullableとNotNullを明確に区別することがわかりました。NotNullの変数にはnullが入ってこないので,これを扱ううえでNPEは起こらないので安全です。Nullableの変数にはnullが入る可能性があるのでNPEが起こりそうです。ということでNPEを起こしてみましょうリスト4)⁠

リスト4 NPEを起こしたい

val s: String? = null
s.length() // ここでコンパイルエラー

String?な変数snullを代入して初期化しています。このslengthメソッドを呼び出してNPEを起こそうとしています。が,実際にはコンパイルエラーとなります。KotlinはNPEを起こさせたくないので,NPEの可能性のある操作をコンパイルエラーとするのです。Nullableに対するメソッド,プロパティアクセスは禁止されています。

しかし現実問題,Nullableのメンバにアクセスできないのは不便どころか使い物になりません。もちろんアクセスする方法が用意されています。その方法とは,nullチェックすることです!

リスト5のように変数snullでないことを確認すると,それが保証される範囲内(ifのブロック内)sをNotNullとして扱えるようになります。s"Hello"が代入されていればリスト5を実行すると「5」と出力されます。

リスト5 nullチェックするとNotNullになる

if(s != null) {
  println(s.length())
}

著者プロフィール

長澤太郎(ながさわたろう)

早稲田大学情報理工学科を2012年に卒業。同年入社したメーカー系SIerを経て,2013年にエムスリー株式会社へ入社。以来,世界の医療を変革するためソフトウェアエンジニアとして従事。

日本Kotlinユーザグループ代表,日本Javaユーザグループ幹事を務める。国内初となるKotlin入門書「Kotlinスタートブック」(リックテレコム)の著者。

ビールとディズニーが大好き。

コメント

コメントの記入