2012年2月18日土曜日

[Kotlin]安全なnull参照

Kotlinではxnull参照が型システムでコントロールされているので、NullPointerExceptionが発生することがないようです。

実際にサンプルコードで動作を確認してみよ。

例1:nullを許可しない変数

通常の変数宣言は、nullを許容しない変数となります。つまり、nullを代入することが出来ない変数なので、
Javaの実装でよくあるnullだったらどうしよう・・・という分岐が必要なくなります。

例えば、以下のコードはnullを許容しない変数にたいするnull代入となるためコンパイルエラーとなります。
var s:String
s = null

例2:nullを許可する変数

nullを許容する変数は、型の末尾に「?」を付加する必要があります。これにより、nullを変数に代入可能となります。
また、その変数を参照する際には、変数の末尾に「?」を付加して参照しないとコンパイルエラーになります。

このnull許可変数は、参照した際にnullの場合はnullが返却されるようになっています。JavaのようにNullPointerExceptionが発生することはないわけですね。

// 型の末尾に「?」を付加する。
var s:String? = "hoge"
val length = s?.length() // -> 4

// 変数がnullの場合に、参照を行うと戻り値もnullとなります。
s = null
val length2 = s?.length() // -> null

例3:Javaと同じようにNullPointerExceptionを送出したい場合

sure関数を使用すると、nullの場合にNullPointerExceptionが送出されます。
var s:String? = "1"
var length = s.sure().length()      // nullでは無いので、「1」が返却されます。

s = null;
length = s.sure().length()          // nullなのでNullPointerExceptionが送出されます。

実際に送出された例外のStackTraceです。
Exception in thread "main" jet.runtime.Intrinsics$JetNullPointerException
 at nullCheck.namespace.main(hoge.kt:21)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

例4:Elvis operator(null値の判定で使用する)

nullの場合に、何か別の値を返す場合に使用するらしいです。

例えば、Javaで文字列の長さを返す際にその値がnullの場合は、-1を返すとする場合は、
以下のように三項演算子を使うと思います。
s != null ? s.length() : -1
これを、KotlinのElvis operatorで書くとこんな感じになります。
null意外の場合は、「:」の左側の式の結果(この場合だとlengthの結果)が返却され、nullの場合は「:」の右側の結果が返却されるようです。
b?.length() ?: -1

例5:安全なキャスト

安全にキャストを行うためには、「as?」を使用する必要があります。
「as?」を使用した場合、その型にキャストができない方の場合にはnullが返却されます。
「as」を使用した場合には、TypeCastExceptionが送出されます。

val s = "123";
var i1:Int = s as Int    // これは、TypeCastExceptionが送出されます。
var i2:Int? = s as? Int  // これは、nullが返却されます。


Collection系のオブジェクトから値を代入したときにその値がnullだった場合どんな動きをするんだ?
これは、コンパイル時にチェックするのは不可能だし、どんな動きをするんだろう?と思って調べてみました。

調べたときにつかったコード
val arr:Array<String?> = array(null)   // nullを許容するArray
val s:String = arr[0]                  // nullが代入出来る。
s.length()                             // ぬるぽではなく、TypeCastExceptionが発生

nullが代入されたnull不可変数を参照したら、まさかのTypeCastExceptionが発生しました。
これだったらNullPointerExceptionのが分かりやすいんじゃね?TypeCastExceptionじゃ原因特定出来る気がしない・・・。
この辺はこれから改善されていくかもしれませんが。

ScalaのOptionなるものがあったからこんどScalaのほうもちゃんと勉強してみよう。