とはいえ、nullが必要になる場面が現実的には多々存在するので、ここからはNull許容型の使い方を見ていきましょう。リスト8のように、Null許容型のプロパティにアクセスすると、コンパイルエラーが発生します。
var a: String = "はろー" println(a.length) //非Null型の参照は当然問題なし var b: String? = "はろー" println(b.length) //Null許容型を参照するとコンパイルエラー
すぐ上の行で変数bにnullではない文字列を代入していますので、本来このコードは動くはずですが、コンパイラは「いきなりNull許容型を参照することはまかりならぬ」として通してくれません。また、リスト7のように、「Null許容型変数を非Null型変数に代入する」こともできません。
var b: String? = "はろー" var c: String = b //コンパイルエラー
何と、String型とString?型は全く別の型で互換性がありません(*3)。
*3)StringはString?の子クラスのような扱いになるため、逆に「Null許容型変数に非Null型変数を代入する」場合は問題なく動作します。
何とも融通が利かないと感じるかもしれませんが、コンパイル時のこの厳密な型チェックが非常な安心感をもたらしてくれるのです。
Null許容型を参照したり、非Null型に代入したりしたい場合は幾つかの方法があります。まず、リスト10のように明確にnullチェックをする方法です。nullチェックさえ済んでしまえば、参照も非Null型への代入も自由に行えます。
var b: String? = "はろー" if(b != null){ //nullチェック println(b.length) //参照できる var c: String = b //非Null型に代入できる } //nullチェックしたifのスコープから外れているためコンパイルエラー println(b.length) when{ //whenでnullチェックしてもOK b != null -> println(b.length) }
Null許容型を参照するための別の方法は、リスト11のように「?.」という演算子を使う方法です。
var d: String? = null println(d?.length) //エラーは起きない。「null」が表示される var e: String? = "はろはろ" println(e?.length) //「4」が表示される
?.演算子(safe call operator。訳語は安全呼び出し演算子、セーフコール演算子など)は、「第1項(演算子の前)がnullの場合はnullを、そうではない場合は第2項(演算子の後)の結果を返す」演算子です。リスト11では、変数の値がnullであってもなくても、コンパイルエラーもNullPointerExceptionも発生することなく、その値を参照できています。ここではプロパティを参照していますが、メソッド呼び出しも同様に可能です。
?.演算子の効能を見てみましょう。リスト12は、深いオブジェクト階層にある値を参照する例を、JavaとKotlinで書いたものです。
value = null; //nullチェックが多重に必要 if(obj != null){ if(obj.param1 != null){ if(obj.param1.param2 != null){ value = obj.param1.param2.param3; } } } ↓ //Kotlinで同じことをする。?.演算子を連ねれば1行で完了 value = obj?.param1?.param2?.param3
もはや語るまでもないという印象ですね。Javaだと多重のNullチェックが必要なのに対し、Kotlinでは1行で書けます。連続して呼び出している?.演算子のどこかで結果がnullになったとしても、以降の?.演算子は自動的にnullを返すわけです。
ちなみに、C#ではNull条件演算子と呼ばれる、同じ記法(?.)の演算子があり、使い方もほぼ同じなので、C#プログラマーにとっては、すっかりおなじみの演算子でしょう。
?:(エルビス)演算子(*4)は、「第1項がnullではない場合には第1項を、nullの場合には第2項を返す」演算子です。リスト13では、Null許容型を返すgetName関数の戻り値を変数に代入しています。もし戻り値がnullだった場合には?:演算子の後ろの「名無しの権兵衛」という値が設定されます。
*4)不思議な名称ですが、「?:」がエルビス・プレスリーの顔文字に似ているから、だそうです……。
//getName関数はNull許容型のString?を返す fun getName(): String?{……} …… //?:演算子でgetName関数の結果がnullだった場合の値を設定 val name2 :String = getName() ?: "名なしの権兵衛" println(name2)
何かのデフォルト値を設定する場合などに使えそうですね。なお、C#ではnull合体演算子(記法は??)という、?:演算子に似た機能が利用可能です。
この他、NullPointerExceptionが忘れられない人のために「nullチェックしていなくても、Null許容型を無理やり非Null型として扱う」!!演算子という演算子も用意されています。これを使えばリスト14のようにKotlinにおいてもNullPointerExceptionを実現(?)できます。ただ、あえてnull安全の世界から飛び出すこの演算子、明確な用途がない限り普段は使うことはなさそうですね。
//!!演算子を使ってnullの入ったNull許容型を無理やり非Null型に変換して参照 var f: String? = null //コンパイルはできるが、実行時にめでたくNullPointerException発生 println(f!!.length)
本文ではNull参照問題を取り上げるため、主に参照型のケースを扱いましたが、Javaのプリミティブ型に相当するInt、Longといった型においても、Null許容型を使えます。もともとInt、Longなどにnullを代入することはできませんが、参照型の場合と同様にInt?、Long?のように「?」を末尾に付けることで、Null許容型の変数として扱うことが可能です。詳しくはレファレンスなどをご確認ください。
今回はnull安全について熱く語ってみましたが、いかがだったでしょうか。nullが不要なケースでは全く意識することなく書けますし、nullをハンドリングする必要がある場合でもnull安全のメリットは大きいです。Javaではnullチェックを忘れると実行時にNullPointerExceptionが投げられますが、Kotlinであればnullチェックを忘れてもコンパイル時に気付くことができるのです。
こうした言語レベルでのnull安全がごくシンプルなアプローチで出来上がっていることに魅力を感じていただけならば、ぜひKotlinを使って体感してみてください。nullの落とし穴がどこにあるか分からない世界にはもう戻れなくなるかもしれません……。
次回以降はKotlinにおけるクラス関連のもろもろの機能を解説します。お楽しみに。
Copyright © ITmedia, Inc. All Rights Reserved.